Merge "Update signature of FontManager#updateFontFile()." into sc-dev
diff --git a/Android.bp b/Android.bp
index 7e68986..240d803 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,7 @@
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
+ ":idmap2_aidl",
":idmap2_core_aidl",
":incidentcompanion_aidl",
":inputconstants_aidl",
@@ -402,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",
@@ -422,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",
@@ -442,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 8723515..73ca0cc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -108,40 +108,53 @@
* 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.
@@ -149,16 +162,32 @@
* <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.
+ * 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
*/
- // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
- // exposed.
public void setSchema(
@NonNull SetSchemaRequest request,
@NonNull @CallbackExecutor Executor executor,
@@ -195,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);
}
@@ -258,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
@@ -299,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
@@ -379,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(
@@ -442,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.
*
@@ -481,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
@@ -522,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/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 271129b..a45fa39 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -41,6 +41,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -58,8 +59,11 @@
private PackageManagerInternal mPackageManagerInternal;
private ImplInstanceManager mImplInstanceManager;
- // Cache of unlocked user ids so we don't have to query UserManager service each time.
- private final Set<Integer> mUnlockedUserIds = new ArraySet<>();
+ // Cache of unlocked user ids so we don't have to query UserManager service each time. The
+ // "locked" suffix refers to the fact that access to the field should be locked; unrelated to
+ // the unlocked status of user ids.
+ @GuardedBy("mUnlockedUserIdsLocked")
+ private final Set<Integer> mUnlockedUserIdsLocked = new ArraySet<>();
public AppSearchManagerService(Context context) {
super(context);
@@ -74,7 +78,9 @@
@Override
public void onUserUnlocked(@NonNull TargetUser user) {
- mUnlockedUserIds.add(user.getUserIdentifier());
+ synchronized (mUnlockedUserIdsLocked) {
+ mUnlockedUserIdsLocked.add(user.getUserIdentifier());
+ }
}
private class Stub extends IAppSearchManager.Stub {
@@ -108,7 +114,7 @@
schemasPackageAccessibleBundles.entrySet()) {
List<PackageIdentifier> packageIdentifiers =
new ArrayList<>(entry.getValue().size());
- for (int i = 0; i < packageIdentifiers.size(); i++) {
+ for (int i = 0; i < entry.getValue().size(); i++) {
packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
}
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
@@ -503,9 +509,11 @@
}
private void verifyUserUnlocked(int callingUserId) {
- if (!mUnlockedUserIds.contains(callingUserId)) {
- throw new IllegalStateException(
- "User " + callingUserId + " is locked or not running.");
+ synchronized (mUnlockedUserIdsLocked) {
+ if (!mUnlockedUserIdsLocked.contains(callingUserId)) {
+ throw new IllegalStateException(
+ "User " + callingUserId + " is locked or not running.");
+ }
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 5ea2a02..82319d4 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import java.io.File;
@@ -43,7 +44,9 @@
private static ImplInstanceManager sImplInstanceManager;
- private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
+ @GuardedBy("mInstancesLocked")
+ private final SparseArray<AppSearchImpl> mInstancesLocked = new SparseArray<>();
+
private final String mGlobalQuerierPackage;
private ImplInstanceManager(@NonNull String globalQuerierPackage) {
@@ -81,19 +84,16 @@
* @return An initialized {@link AppSearchImpl} for this user
*/
@NonNull
- public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
- throws AppSearchException {
- AppSearchImpl instance = mInstances.get(userId);
- if (instance == null) {
- synchronized (ImplInstanceManager.class) {
- instance = mInstances.get(userId);
- if (instance == null) {
- instance = createImpl(context, userId);
- mInstances.put(userId, instance);
- }
+ public AppSearchImpl getAppSearchImpl(
+ @NonNull Context context, @UserIdInt int userId) throws AppSearchException {
+ synchronized (mInstancesLocked) {
+ AppSearchImpl instance = mInstancesLocked.get(userId);
+ if (instance == null) {
+ instance = createImpl(context, userId);
+ mInstancesLocked.put(userId, instance);
}
+ return instance;
}
- return instance;
}
private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 64dc972..babcd25 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -332,10 +332,8 @@
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
- // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax
- // nested document uri rules gets synced down.
GenericDocument packageAccessibleDocument =
- new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE)
+ new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE)
.setNamespace(NAMESPACE)
.setPropertyString(
PACKAGE_NAME_PROPERTY,
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 8bff720..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;
@@ -317,7 +363,7 @@
}
Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
- new ArrayMap<>(schemasNotPlatformSurfaceable.size());
+ new ArrayMap<>(schemasPackageAccessible.size());
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
@@ -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/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index eb1623e..6595d8d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -37,8 +37,9 @@
import java.util.concurrent.Executors;
/**
- * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
- * a consistent interface.
+ * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
+ * consistent interface.
+ *
* @hide
*/
public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@@ -47,7 +48,13 @@
@NonNull
public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
- Context context = ApplicationProvider.getApplicationContext();
+ return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
+ }
+
+ /** Only for use when called from a non-instrumented context. */
+ @NonNull
+ public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
+ @NonNull Context context) {
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
ExecutorService executor = Executors.newCachedThreadPool();
@@ -62,7 +69,6 @@
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
mGlobalSearchSession = Preconditions.checkNotNull(session);
mExecutor = Preconditions.checkNotNull(executor);
-
}
@NonNull
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 8e62c0e..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,20 +77,24 @@
* 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
* efficiently.
*
- * <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.
+ * <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>Migration: make non-backwards-compatible changes will delete all stored documents in old
* schema. You can save your documents by setting {@link
@@ -109,15 +113,11 @@
* 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
*/
- // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
- // exposed.
@NonNull
ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request);
@@ -146,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);
@@ -221,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/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 82e967a..3cefe65 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -64,7 +64,7 @@
* and which {@link JobServiceContext} to run each job on.
*/
class JobConcurrencyManager {
- private static final String TAG = JobSchedulerService.TAG;
+ private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
private static final boolean DEBUG = JobSchedulerService.DEBUG;
static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
@@ -321,13 +321,14 @@
}
}
+ /** Return {@code true} if the state was updated. */
@GuardedBy("mLock")
- private void refreshSystemStateLocked() {
+ private boolean refreshSystemStateLocked() {
final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
// Only refresh the information every so often.
if (nowUptime < mNextSystemStateRefreshTime) {
- return;
+ return false;
}
final long start = mStatLogger.getTime();
@@ -340,11 +341,14 @@
}
mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
+ return true;
}
@GuardedBy("mLock")
private void updateCounterConfigLocked() {
- refreshSystemStateLocked();
+ if (!refreshSystemStateLocked()) {
+ return;
+ }
final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
@@ -437,9 +441,10 @@
// (sharing the same Uid as nextPending)
int minPriorityForPreemption = Integer.MAX_VALUE;
int selectedContextId = -1;
- int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ int allWorkTypes = getJobWorkTypes(nextPending);
+ int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
- for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+ for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
JobStatus job = contextIdToJobMap[j];
int preferredUid = preferredUidForContext[j];
if (job == null) {
@@ -483,7 +488,7 @@
if (startingJob) {
// Increase the counters when we're going to start a job.
workTypeForContext[selectedContextId] = workType;
- mWorkCountTracker.stageJob(workType);
+ mWorkCountTracker.stageJob(workType, allWorkTypes);
}
}
if (DEBUG) {
@@ -578,8 +583,10 @@
JobStatus highestPriorityJob = null;
int highPriWorkType = workType;
+ int highPriAllWorkTypes = workType;
JobStatus backupJob = null;
int backupWorkType = WORK_TYPE_NONE;
+ int backupAllWorkTypes = WORK_TYPE_NONE;
for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i);
@@ -589,11 +596,12 @@
if (worker.getPreferredUid() != nextPending.getUid()) {
if (backupJob == null) {
- int workAsType =
- mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ int allWorkTypes = getJobWorkTypes(nextPending);
+ int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType != WORK_TYPE_NONE) {
backupJob = nextPending;
backupWorkType = workAsType;
+ backupAllWorkTypes = allWorkTypes;
}
}
continue;
@@ -611,7 +619,8 @@
// reserved slots. We should just run the highest priority job we can find,
// though it would be ideal to use an available WorkType slot instead of
// overloading slots.
- final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ highPriAllWorkTypes = getJobWorkTypes(nextPending);
+ final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
// Just use the preempted job's work type since this new one is technically
// replacing it anyway.
@@ -624,7 +633,7 @@
if (DEBUG) {
Slog.d(TAG, "Running job " + jobStatus + " as preemption");
}
- mWorkCountTracker.stageJob(highPriWorkType);
+ mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
startJobLocked(worker, highestPriorityJob, highPriWorkType);
} else {
if (DEBUG) {
@@ -635,7 +644,7 @@
if (DEBUG) {
Slog.d(TAG, "Running job " + jobStatus + " instead");
}
- mWorkCountTracker.stageJob(backupWorkType);
+ mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
startJobLocked(worker, backupJob, backupWorkType);
}
}
@@ -647,6 +656,7 @@
// find.
JobStatus highestPriorityJob = null;
int highPriWorkType = workType;
+ int highPriAllWorkTypes = workType;
for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i);
@@ -654,7 +664,8 @@
continue;
}
- final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ final int allWorkTypes = getJobWorkTypes(nextPending);
+ final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
continue;
}
@@ -663,6 +674,7 @@
< nextPending.lastEvaluatedPriority) {
highestPriorityJob = nextPending;
highPriWorkType = workAsType;
+ highPriAllWorkTypes = allWorkTypes;
}
}
@@ -672,7 +684,7 @@
if (DEBUG) {
Slog.d(TAG, "About to run job: " + jobStatus);
}
- mWorkCountTracker.stageJob(highPriWorkType);
+ mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
startJobLocked(worker, highestPriorityJob, highPriWorkType);
}
}
@@ -1102,26 +1114,58 @@
}
void incrementPendingJobCount(int workTypes) {
- // We don't know which type we'll classify the job as when we run it yet, so make sure
- // we have space in all applicable slots.
- if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
- mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
- }
- if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
- mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + 1);
- }
- if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
- mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
- }
- if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
- mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
+ adjustPendingJobCount(workTypes, true);
+ }
+
+ void decrementPendingJobCount(int workTypes) {
+ if (adjustPendingJobCount(workTypes, false) > 1) {
+ // We don't need to adjust reservations if only one work type was modified
+ // because that work type is the one we're using.
+
+ // 0 is WORK_TYPE_NONE.
+ int workType = 1;
+ int rem = workTypes;
+ while (rem > 0) {
+ if ((rem & 1) != 0) {
+ maybeAdjustReservations(workType);
+ }
+ rem = rem >>> 1;
+ workType = workType << 1;
+ }
}
}
- void stageJob(@WorkType int workType) {
+ /** Returns the number of WorkTypes that were modified. */
+ private int adjustPendingJobCount(int workTypes, boolean add) {
+ final int adj = add ? 1 : -1;
+
+ int numAdj = 0;
+ // We don't know which type we'll classify the job as when we run it yet, so make sure
+ // we have space in all applicable slots.
+ if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+ mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
+ mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+ mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+ mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj);
+ numAdj++;
+ }
+
+ return numAdj;
+ }
+
+ void stageJob(@WorkType int workType, int allWorkTypes) {
final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
mNumStartingJobs.put(workType, newNumStartingJobs);
- mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+ decrementPendingJobCount(allWorkTypes);
if (newNumStartingJobs + mNumRunningJobs.get(workType)
> mNumActuallyReservedSlots.get(workType)) {
mNumUnspecializedRemaining--;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 82ee5d8..96f3bcc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
final JobConcurrencyManager mConcurrencyManager;
- static final int MSG_JOB_EXPIRED = 0;
+ static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
static final int MSG_CHECK_JOB = 1;
static final int MSG_STOP_JOB = 2;
static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
+ JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+ if (newJs != null) {
+ // This job was stopped because the app scheduled a new job with the same job ID.
+ // Check if the new job is ready to run.
+ mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+ }
return;
}
@@ -1748,7 +1754,11 @@
@Override
public void onRunJobNow(JobStatus jobStatus) {
- mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+ if (jobStatus == null) {
+ mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+ } else {
+ mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+ }
}
final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
return;
}
switch (message.what) {
- case MSG_JOB_EXPIRED: {
- JobStatus runNow = (JobStatus) message.obj;
- // runNow can be null, which is a controller's way of indicating that its
- // state is such that all ready jobs should be run immediately.
- if (runNow != null) {
- if (!isCurrentlyActiveLocked(runNow)
- && isReadyToBeExecutedLocked(runNow)) {
- mJobPackageTracker.notePending(runNow);
- addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+ case MSG_CHECK_INDIVIDUAL_JOB: {
+ JobStatus js = (JobStatus) message.obj;
+ if (js != null) {
+ if (isReadyToBeExecutedLocked(js)) {
+ mJobPackageTracker.notePending(js);
+ addOrderedItem(mPendingJobs, js, sPendingJobComparator);
}
} else {
- queueReadyJobsForExecutionLocked();
+ Slog.e(TAG, "Given null job to check individually");
}
} break;
case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
// This method will check and capture all ready jobs, so we don't need to keep any messages
// in the queue.
mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+ mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
// MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
// jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
mHandler.removeMessages(MSG_CHECK_JOB);
- // This method will capture all expired jobs that are ready, so there's no need to keep
- // the _EXPIRED messages in the queue.
- mHandler.removeMessages(MSG_JOB_EXPIRED);
if (DEBUG) {
Slog.d(TAG, "queuing all ready jobs for execution:");
}
@@ -3178,7 +3183,7 @@
TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
pw.println();
job.dump(pw, " ", false, nowElapsed);
- int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
+ int priority = evaluateJobPriorityLocked(job);
pw.print(" Evaluated priority: ");
pw.println(JobInfo.getPriorityString(priority));
@@ -3344,7 +3349,7 @@
job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
- evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
+ evaluateJobPriorityLocked(job));
proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
nowUptime - job.madeActive);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d15bae0..da6f9fe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -20,6 +20,7 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.Nullable;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -326,6 +327,7 @@
/**
* Used externally to query the running job. Will return null if there is no job running.
*/
+ @Nullable
JobStatus getRunningJobLocked() {
return mRunningJob;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
}
}
- taskStatus.changedAuthorities = null;
- taskStatus.changedUris = null;
}
taskStatus.changedAuthorities = null;
taskStatus.changedUris = null;
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/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 67fa9bb..a2366df 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -8,9 +8,10 @@
method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes();
method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes();
method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
- method public boolean isHdrTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+ method public boolean isFormatSpecified(@NonNull String);
+ method public boolean isHdrTypeSupported(@NonNull String);
method public boolean isSlowMotionSupported();
- method public boolean isVideoMimeTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+ method public boolean isVideoMimeTypeSupported(@NonNull String);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
}
@@ -24,10 +25,6 @@
method @NonNull public android.media.ApplicationMediaCapabilities build();
}
- public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException {
- ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String);
- }
-
public class MediaCommunicationManager {
method @IntRange(from=1) public int getVersion();
}
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index aefeab6..685cf0d 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -22,7 +22,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.AndroidException;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
@@ -79,17 +78,7 @@
public final class ApplicationMediaCapabilities implements Parcelable {
private static final String TAG = "ApplicationMediaCapabilities";
- /**
- * This exception is thrown when a given format is not specified in the media capabilities.
- */
- public static class FormatNotFoundException extends AndroidException {
- public FormatNotFoundException(@NonNull String format) {
- super(format);
- }
- }
-
/** List of supported video codec mime types. */
- // TODO: init it with avc and mpeg4 as application is assuming to support them.
private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
/** List of unsupported video codec mime types. */
@@ -113,39 +102,54 @@
/**
* Query if a video codec format is supported by the application.
+ * <p>
+ * If the application has not specified supporting the format or not, this will return false.
+ * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+ *
* @param videoMime The mime type of the video codec format. Must be the one used in
* {@link MediaFormat#KEY_MIME}.
* @return true if application supports the video codec format, false otherwise.
- * @throws FormatNotFoundException if the application did not specify the codec either in the
- * supported or unsupported formats.
*/
public boolean isVideoMimeTypeSupported(
- @NonNull String videoMime) throws FormatNotFoundException {
- if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
- return false;
- } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
+ @NonNull String videoMime) {
+ if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
return true;
- } else {
- throw new FormatNotFoundException(videoMime);
}
+ return false;
}
/**
* Query if a HDR type is supported by the application.
+ * <p>
+ * If the application has not specified supporting the format or not, this will return false.
+ * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+ *
* @param hdrType The type of the HDR format.
* @return true if application supports the HDR format, false otherwise.
- * @throws FormatNotFoundException if the application did not specify the format either in the
- * supported or unsupported formats.
*/
public boolean isHdrTypeSupported(
- @NonNull @MediaFeature.MediaHdrType String hdrType) throws FormatNotFoundException {
- if (mUnsupportedHdrTypes.contains(hdrType)) {
- return false;
- } else if (mSupportedHdrTypes.contains(hdrType)) {
+ @NonNull @MediaFeature.MediaHdrType String hdrType) {
+ if (mSupportedHdrTypes.contains(hdrType)) {
return true;
- } else {
- throw new FormatNotFoundException(hdrType);
}
+ return false;
+ }
+
+ /**
+ * Query if a format is specified by the application.
+ * <p>
+ * The format could be either the video format or the hdr format.
+ *
+ * @param format The name of the format.
+ * @return true if application specifies the format, false otherwise.
+ */
+ public boolean isFormatSpecified(@NonNull String format) {
+ if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format)
+ || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) {
+ return true;
+
+ }
+ return false;
}
@Override
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index ce7726a..c924d9a 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -1062,14 +1062,8 @@
"Source video format hint must be set!");
}
- boolean supportHevc = false;
- try {
- supportHevc = mClientCaps.isVideoMimeTypeSupported(
- MediaFormat.MIMETYPE_VIDEO_HEVC);
- } catch (ApplicationMediaCapabilities.FormatNotFoundException ex) {
- // Set to false if application did not specify.
- supportHevc = false;
- }
+ boolean supportHevc = mClientCaps.isVideoMimeTypeSupported(
+ MediaFormat.MIMETYPE_VIDEO_HEVC);
if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals(
mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) {
return true;
diff --git a/api/Android.bp b/api/Android.bp
index d5c6bf6..ac2f083 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}",
@@ -75,6 +76,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 +101,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 +136,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}",
@@ -167,6 +171,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 +206,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}",
@@ -234,6 +240,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/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 312f4ac..05336ba 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -161,6 +161,12 @@
idmap_path.c_str(), uid));
}
+ // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+ // that existing memory maps will continue to be valid and unaffected. The file must be deleted
+ // before attempting to create the idmap, so that if idmap creation fails, the overlay will no
+ // longer be usable.
+ unlink(idmap_path.c_str());
+
const auto target = GetTargetContainer(target_path);
if (!target) {
return error("failed to load target '%s'" + target_path);
@@ -177,10 +183,6 @@
return error(idmap.GetErrorMessage());
}
- // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
- // that existing memory maps will continue to be valid and unaffected.
- unlink(idmap_path.c_str());
-
umask(kIdmapFilePermissionMask);
std::ofstream fout(idmap_path);
if (fout.fail()) {
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/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 90a526b..48aa8b2 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -204,7 +204,6 @@
Landroid/os/IUpdateEngine$Stub;-><init>()V
Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager;
-Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService;
Landroid/os/storage/IObbActionListener$Stub;-><init>()V
Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager;
diff --git a/core/api/current.txt b/core/api/current.txt
index 53be53c..9cbf12f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -849,6 +849,7 @@
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
+ field public static final int knownCerts = 16844330; // 0x101062a
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
@@ -1184,6 +1185,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
@@ -1294,6 +1296,7 @@
field public static final int spinnerMode = 16843505; // 0x10102f1
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
+ field public static final int splashScreenTheme = 16844336; // 0x1010630
field public static final int splitMotionEvents = 16843503; // 0x10102ef
field public static final int splitName = 16844105; // 0x1010549
field public static final int splitTrack = 16843852; // 0x101044c
@@ -1613,6 +1616,7 @@
field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
+ field public static final int windowBackgroundBlurRadius = 16844331; // 0x101062b
field public static final int windowBackgroundFallback = 16844035; // 0x1010503
field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
@@ -1653,6 +1657,10 @@
field public static final int windowShowAnimation = 16842934; // 0x10100b6
field public static final int windowShowWallpaper = 16843410; // 0x1010292
field public static final int windowSoftInputMode = 16843307; // 0x101022b
+ field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
+ field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
+ field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
+ field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
field public static final int windowSplashscreenContent = 16844132; // 0x1010564
field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3
field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
@@ -1739,6 +1747,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
}
@@ -3863,6 +3874,7 @@
method @Nullable public android.net.Uri getReferrer();
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
+ method @NonNull public final android.window.SplashScreen getSplashScreen();
method public int getTaskId();
method public final CharSequence getTitle();
method public final int getTitleColor();
@@ -5586,10 +5598,10 @@
field public static final String EXTRA_PROGRESS = "android.progress";
field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
- field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+ field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
field @Deprecated public static final String EXTRA_SMALL_ICON = "android.icon";
@@ -6651,6 +6663,7 @@
method @NonNull public java.time.LocalTime getCustomNightModeEnd();
method @NonNull public java.time.LocalTime getCustomNightModeStart();
method public int getNightMode();
+ method public void setApplicationNightMode(int);
method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
method public void setNightMode(int);
@@ -6895,6 +6908,7 @@
method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int);
+ method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean);
method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle);
method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -6948,6 +6962,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);
@@ -7071,9 +7086,11 @@
method public boolean isProfileOwnerApp(String);
method public boolean isProvisioningAllowed(@NonNull String);
method public boolean isResetPasswordTokenActive(android.content.ComponentName);
+ method public boolean isSafeOperation(int);
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);
@@ -7175,6 +7192,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);
@@ -7243,7 +7261,7 @@
field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
- field public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
+ field @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
field public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
@@ -7299,6 +7317,7 @@
field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+ field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
@@ -7335,7 +7354,6 @@
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
- field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7489,7 +7507,7 @@
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
method public int describeContents();
- method public int getReason();
+ method @NonNull public java.util.List<java.lang.Integer> getReasons();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
}
@@ -8291,6 +8309,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);
@@ -8298,11 +8317,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 {
@@ -8355,6 +8376,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 {
@@ -9816,6 +9838,7 @@
method public int getMimeTypeCount();
method public long getTimestamp();
method public boolean hasMimeType(String);
+ method public boolean isStyledText();
method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
@@ -10222,12 +10245,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);
@@ -10440,6 +10466,7 @@
field public static final String USAGE_STATS_SERVICE = "usagestats";
field public static final String USB_SERVICE = "usb";
field public static final String USER_SERVICE = "user";
+ field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
field public static final String VIBRATOR_SERVICE = "vibrator";
field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
field public static final String WALLPAPER_SERVICE = "wallpaper";
@@ -10980,6 +11007,7 @@
field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+ field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
@@ -12772,6 +12800,7 @@
method @NonNull public android.content.pm.ShortcutInfo.Builder setPersons(@NonNull android.app.Person[]);
method @NonNull public android.content.pm.ShortcutInfo.Builder setRank(int);
method @NonNull public android.content.pm.ShortcutInfo.Builder setShortLabel(@NonNull CharSequence);
+ method @NonNull public android.content.pm.ShortcutInfo.Builder setStartingTheme(int);
}
public class ShortcutManager {
@@ -16509,9 +16538,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 {
@@ -21891,6 +21924,7 @@
field public static final String KEY_COLOR_RANGE = "color-range";
field public static final String KEY_COLOR_STANDARD = "color-standard";
field public static final String KEY_COLOR_TRANSFER = "color-transfer";
+ field public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
field public static final String KEY_COMPLEXITY = "complexity";
field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
field public static final String KEY_DURATION = "durationUs";
@@ -23033,10 +23067,12 @@
method @Deprecated public int getStreamType();
method public String getTitle(android.content.Context);
method public float getVolume();
+ method public boolean isHapticGeneratorEnabled();
method public boolean isLooping();
method public boolean isPlaying();
method public void play();
method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+ method public boolean setHapticGeneratorEnabled(boolean);
method public void setLooping(boolean);
method @Deprecated public void setStreamType(int);
method public void setVolume(float);
@@ -31625,6 +31661,7 @@
method @NonNull public int[] areEffectsSupported(@NonNull int...);
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+ method public int getId();
method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
@@ -31639,10 +31676,12 @@
}
public abstract class VibratorManager {
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
method @NonNull public abstract android.os.Vibrator getVibrator(int);
method @NonNull public abstract int[] getVibratorIds();
- method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -38066,6 +38105,10 @@
method public final void setNotificationsShown(String[]);
method public final void snoozeNotification(String, long);
method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+ field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
+ field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
+ field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
+ field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38074,6 +38117,7 @@
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+ field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types";
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
@@ -42029,8 +42073,9 @@
field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
- field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+ field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
}
public class TelephonyManager {
@@ -49348,6 +49393,7 @@
method public void setAllowEnterTransitionOverlap(boolean);
method public void setAllowReturnTransitionOverlap(boolean);
method public void setAttributes(android.view.WindowManager.LayoutParams);
+ method public void setBackgroundBlurRadius(int);
method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
method public void setBackgroundDrawableResource(@DrawableRes int);
method public void setCallback(android.view.Window.Callback);
@@ -53582,16 +53628,19 @@
public class EdgeEffect {
ctor public EdgeEffect(android.content.Context);
+ ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
method public boolean draw(android.graphics.Canvas);
method public void finish();
method @Nullable public android.graphics.BlendMode getBlendMode();
method @ColorInt public int getColor();
+ method public float getDistance();
method public int getMaxHeight();
method public int getType();
method public boolean isFinished();
method public void onAbsorb(int);
method public void onPull(float);
method public void onPull(float, float);
+ method public float onPullDistance(float, float);
method public void onRelease();
method public void setBlendMode(@Nullable android.graphics.BlendMode);
method public void setColor(@ColorInt int);
@@ -54604,6 +54653,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);
@@ -55801,6 +55851,25 @@
}
+package android.window {
+
+ public interface SplashScreen {
+ method public void setOnExitAnimationListener(@Nullable android.window.SplashScreen.OnExitAnimationListener);
+ }
+
+ public static interface SplashScreen.OnExitAnimationListener {
+ method public void onSplashScreenExit(@NonNull android.window.SplashScreenView);
+ }
+
+ public final class SplashScreenView extends android.widget.FrameLayout {
+ method public long getIconAnimationDurationMillis();
+ method public long getIconAnimationStartMillis();
+ method @Nullable public android.view.View getIconView();
+ method public void remove();
+ }
+
+}
+
package javax.microedition.khronos.egl {
public interface EGL {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4e25625..b57fdf1 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
}
}
@@ -223,6 +229,14 @@
field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
}
+ public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+ ctor public VpnTransportInfo(int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+ field public final int type;
+ }
+
}
package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c34dadd..f330a93 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";
@@ -82,6 +83,7 @@
field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
+ field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
@@ -179,6 +181,7 @@
field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+ field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -244,6 +247,7 @@
field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+ field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
@@ -340,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 {
@@ -677,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
}
@@ -869,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();
@@ -904,8 +909,8 @@
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
- field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
- field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
+ field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
+ field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
@@ -1849,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();
@@ -2522,7 +2528,8 @@
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
- field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+ field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+ field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version";
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
@@ -2631,6 +2638,7 @@
field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
+ field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -3535,6 +3543,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
@@ -7115,6 +7124,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -7136,6 +7146,10 @@
field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
}
+ public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
+ method public void onComplete();
+ }
+
@Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
method @Deprecated public void onTetheringFailed();
@@ -7220,6 +7234,7 @@
method public void close();
method @NonNull public String getInterfaceName();
method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
}
public static class IpSecTransform.Builder {
@@ -7363,6 +7378,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
@@ -7377,6 +7393,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[]);
@@ -7492,6 +7509,26 @@
ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
}
+ public final class OemNetworkPreferences implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR;
+ field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1
+ field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2
+ field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3
+ field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4
+ field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0
+ }
+
+ public static final class OemNetworkPreferences.Builder {
+ ctor public OemNetworkPreferences.Builder();
+ ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences);
+ method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int);
+ method @NonNull public android.net.OemNetworkPreferences build();
+ method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String);
+ }
+
public abstract class QosCallback {
ctor public QosCallback();
method public void onError(@NonNull android.net.QosCallbackException);
@@ -8530,7 +8567,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);
@@ -8600,6 +8637,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 {
@@ -9550,10 +9588,12 @@
}
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+ method @Nullable public int[] getAttestationIds();
method public int getNamespace();
}
public static final class KeyGenParameterSpec.Builder {
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
}
@@ -10386,6 +10426,7 @@
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException;
+ method public long onGetAnrDelayMillis(@NonNull String, int);
method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException;
method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException;
field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
@@ -11077,6 +11118,8 @@
}
public static final class CarrierConfigManager.Wifi {
+ field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool";
+ field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool";
field public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT = "wifi.hotspot_maximum_client_count";
field public static final String KEY_PREFIX = "wifi.";
field public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED = "wifi.suggestion_ssid_list_with_mac_randomization_disabled";
@@ -11978,6 +12021,7 @@
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
+ field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ee5cecb..86fe8c3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -391,11 +391,11 @@
method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
+ method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
- method @NonNull public static String unsafeOperationReasonToString(int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
@@ -425,6 +425,7 @@
field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c
field public static final int OPERATION_REMOVE_USER = 6; // 0x6
field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d
+ field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff
field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e
field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
@@ -460,7 +461,6 @@
field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
- field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
}
public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
@@ -693,6 +693,7 @@
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
method public void holdLock(android.os.IBinder, int);
+ method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
@@ -915,6 +916,8 @@
method @NonNull public int[] getSupportedStates();
method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+ field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
+ field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
}
public static interface DeviceStateManager.DeviceStateListener {
@@ -1347,6 +1350,12 @@
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public class NetworkPolicyManager {
+ method public boolean getRestrictBackground();
+ method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration);
+ method public void setRestrictBackground(boolean);
+ }
+
public class NetworkStack {
method public static void setServiceForTest(@Nullable android.os.IBinder);
}
@@ -1373,6 +1382,32 @@
field public static final int RESOURCES_SDK_INT;
}
+ public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+ method public abstract long getDuration();
+ }
+
+ public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
+ method public long getDuration();
+ method @NonNull public android.os.VibrationEffect getEffect();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
+ }
+
+ public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
+ method @NonNull public java.util.List<java.lang.Integer> getDelays();
+ method public long getDuration();
+ method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
+ }
+
+ public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
+ method public long getDuration();
+ method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
+ }
+
public class DeviceIdleManager {
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
@@ -2696,6 +2731,10 @@
field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
}
+ public final class SplashScreenView extends android.widget.FrameLayout {
+ method @Nullable public android.view.View getBrandingView();
+ }
+
public final class StartingWindowInfo implements android.os.Parcelable {
ctor public StartingWindowInfo();
method public int describeContents();
@@ -2715,6 +2754,7 @@
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
+ method @BinderThread public void copySplashScreenView(int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 3c67e44..87fb5b1 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -487,6 +487,8 @@
GetterSetterNames: android.location.LocationRequest#isLowPowerMode():
+GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground():
+ Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground`
GetterSetterNames: android.os.IncidentReportArgs#isAll():
GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied():
diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/core/java/android/accounts/OWNERS
+++ b/core/java/android/accounts/OWNERS
@@ -3,7 +3,6 @@
sandrakwan@google.com
hackbod@google.com
svetoslavganov@google.com
-moltmann@google.com
fkupolov@google.com
yamasani@google.com
omakoto@google.com
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4728f11..992d054 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -139,6 +139,8 @@
import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -961,6 +963,10 @@
private UiTranslationController mUiTranslationController;
+ private SplashScreen mSplashScreen;
+ /** @hide */
+ SplashScreenView mSplashScreenView;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -1603,6 +1609,23 @@
}
/**
+ * Get the interface that activity use to talk to the splash screen.
+ * @see SplashScreen
+ */
+ public final @NonNull SplashScreen getSplashScreen() {
+ return getOrCreateSplashScreen();
+ }
+
+ private SplashScreen getOrCreateSplashScreen() {
+ synchronized (this) {
+ if (mSplashScreen == null) {
+ mSplashScreen = new SplashScreen.SplashScreenImpl(this);
+ }
+ return mSplashScreen;
+ }
+ }
+
+ /**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
* <code>persistAcrossReboots</code>.
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 401f8cc..e3b5e9a 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -46,9 +46,9 @@
}
/** Reports {@link Activity#onResume()} is done. */
- public void activityResumed(IBinder token) {
+ public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
try {
- getActivityClientController().activityResumed(token);
+ getActivityClientController().activityResumed(token, handleSplashScreenExit);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -488,6 +488,17 @@
}
}
+ /**
+ * Reports the splash screen view has attached to client.
+ */
+ void reportSplashScreenAttached(IBinder token) {
+ try {
+ getActivityClientController().splashScreenAttached(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
public static ActivityClient getInstance() {
return sInstance.get();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 28da1c3..73cc13c8 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -160,6 +160,12 @@
public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
/**
+ * Specific a theme for a splash screen window.
+ * @hide
+ */
+ public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
+
+ /**
* Callback for when the last frame of the animation is played.
* @hide
*/
@@ -398,6 +404,7 @@
private IBinder mLaunchCookie;
private IRemoteTransition mRemoteTransition;
private boolean mOverrideTaskTransition;
+ private int mSplashScreenThemeResId;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1147,6 +1154,7 @@
mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
KEY_REMOTE_TRANSITION));
mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
+ mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME);
}
/**
@@ -1333,6 +1341,14 @@
}
/**
+ * Gets whether the activity want to be launched as other theme for the splash screen.
+ * @hide
+ */
+ public int getSplashScreenThemeResId() {
+ return mSplashScreenThemeResId;
+ }
+
+ /**
* Sets whether the activity is to be launched into LockTask mode.
*
* Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1838,6 +1854,9 @@
if (mOverrideTaskTransition) {
b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
}
+ if (mSplashScreenThemeResId != 0) {
+ b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId);
+ }
return b;
}
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 70fa444..233f737 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -37,6 +38,7 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import java.util.List;
@@ -243,6 +245,22 @@
}
/**
+ * Notify the server that splash screen of the given task has been copied"
+ *
+ * @param taskId Id of task to handle the material to reconstruct the splash screen view.
+ * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+ * @hide
+ */
+ public void onSplashScreenViewCopyFinished(int taskId,
+ @Nullable SplashScreenViewParcelable parcelable) {
+ try {
+ getService().onSplashScreenViewCopyFinished(taskId, parcelable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the default limit on the number of recents that an app can make.
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bb6a774..3d9f612 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -171,12 +171,15 @@
import android.view.ViewDebug;
import android.view.ViewManager;
import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import android.webkit.WebView;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -185,6 +188,7 @@
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
@@ -227,6 +231,7 @@
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -285,6 +290,7 @@
/** Use background GC policy and default JIT threshold. */
private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
+ private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000;
/**
* Denotes an invalid sequence number corresponding to a process state change.
*/
@@ -486,6 +492,8 @@
final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
= new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
+ private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
+
final GcIdler mGcIdler = new GcIdler();
final PurgeIdler mPurgeIdler = new PurgeIdler();
@@ -1930,6 +1938,8 @@
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
+ public static final int REMOVE_SPLASH_SCREEN_VIEW = 172;
+
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -1976,6 +1986,8 @@
case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+ case REMOVE_SPLASH_SCREEN_VIEW:
+ return "REMOVE_SPLASH_SCREEN_VIEW";
}
}
return Integer.toString(code);
@@ -2169,6 +2181,9 @@
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
handleFinishInstrumentationWithoutRestart();
break;
+ case REMOVE_SPLASH_SCREEN_VIEW:
+ handleRemoveSplashScreenView((ActivityClientRecord) msg.obj);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -3955,6 +3970,106 @@
}
/**
+ * Register a splash screen manager to this process.
+ */
+ public void registerSplashScreenManager(
+ @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
+ synchronized (this) {
+ mSplashScreenGlobal = manager;
+ }
+ }
+
+ @Override
+ public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
+ synchronized (this) {
+ return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
+ }
+ }
+
+ @Override
+ public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+ final DecorView decorView = (DecorView) r.window.peekDecorView();
+ if (parcelable != null && decorView != null) {
+ createSplashScreen(r, decorView, parcelable);
+ } else {
+ // shouldn't happen!
+ Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
+ }
+ }
+
+ private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
+ SplashScreenView.SplashScreenViewParcelable parcelable) {
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
+ final SplashScreenView view = builder.createFromParcel(parcelable).build();
+ decorView.addView(view);
+ view.cacheRootWindow(r.window);
+ view.makeSystemUIColorsTransparent();
+ r.activity.mSplashScreenView = view;
+ view.requestLayout();
+ // Ensure splash screen view is shown before remove the splash screen window.
+ final ViewRootImpl impl = decorView.getViewRootImpl();
+ final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
+ final AtomicBoolean notified = new AtomicBoolean();
+ if (hardwareEnabled) {
+ final Runnable frameCommit = new Runnable() {
+ @Override
+ public void run() {
+ view.post(() -> {
+ if (!notified.get()) {
+ view.getViewTreeObserver().unregisterFrameCommitCallback(this);
+ ActivityClient.getInstance().reportSplashScreenAttached(
+ r.token);
+ notified.set(true);
+ }
+ });
+ }
+ };
+ view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
+ } else {
+ final ViewTreeObserver.OnDrawListener onDrawListener =
+ new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ view.post(() -> {
+ if (!notified.get()) {
+ view.getViewTreeObserver().removeOnDrawListener(this);
+ ActivityClient.getInstance().reportSplashScreenAttached(
+ r.token);
+ notified.set(true);
+ }
+ });
+ }
+ };
+ view.getViewTreeObserver().addOnDrawListener(onDrawListener);
+ }
+ }
+
+ @Override
+ public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
+ if (r.activity.mSplashScreenView != null) {
+ Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r);
+ mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT);
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.dispatchOnExitAnimation(r.token,
+ r.activity.mSplashScreenView);
+ }
+ }
+ }
+ }
+
+ /**
+ * Force remove splash screen view.
+ */
+ private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) {
+ if (r.activity.mSplashScreenView != null) {
+ r.activity.mSplashScreenView.remove();
+ r.activity.mSplashScreenView = null;
+ }
+ }
+
+ /**
* Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
* return to its previous state. This allows activities that rely on onUserLeaveHint instead of
* onPictureInPictureRequested to enter picture-in-picture.
@@ -5174,6 +5289,11 @@
r.setState(ON_DESTROY);
mLastReportedWindowingMode.remove(r.activity.getActivityToken());
schedulePurgeIdler();
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.tokenDestroyed(r.token);
+ }
+ }
// updatePendingActivityConfiguration() reads from mActivities to update
// ActivityClientRecord which runs in a different thread. Protect modifications to
// mActivities to avoid race.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 0e1c827..cf5fd14 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
import android.os.IBinder;
import android.util.MergedConfiguration;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
@@ -158,6 +159,16 @@
/** Request that an activity enter picture-in-picture. */
public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
+ /** Whether the activity want to handle splash screen exit animation */
+ public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token);
+
+ /** Attach a splash screen window view to the top of the activity */
+ public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+ @NonNull SplashScreenViewParcelable parcelable);
+
+ /** Hand over the splash screen window view to the activity */
+ public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent);
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/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9d3286f..bb743b8 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -34,7 +34,7 @@
*/
interface IActivityClientController {
oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
- oneway void activityResumed(in IBinder token);
+ oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
oneway void activityTopResumedStateLost();
/**
* Notifies that the activity has completed paused. This call is not one-way because it can make
@@ -142,4 +142,7 @@
* on the back stack.
*/
oneway void onBackPressedOnTaskRoot(in IBinder token);
+
+ /** Reports that the splash screen view has attached to activity. */
+ oneway void splashScreenAttached(in IBinder token);
}
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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 38a3e70..542f754 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -70,6 +70,7 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.IResultReceiver;
@@ -348,4 +349,10 @@
* Clears launch params for given packages.
*/
void clearLaunchParamsForPackages(in List<String> packageNames);
+
+ /**
+ * A splash screen view has copied.
+ */
+ void onSplashScreenViewCopyFinished(int taskId,
+ in SplashScreenView.SplashScreenViewParcelable material);
}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
similarity index 73%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/app/ILocalWallpaperColorConsumer.aidl
index 14d57bf..28b11ec 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
@@ -14,6 +14,14 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app;
-parcelable ExternalTimeSuggestion;
+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/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 0ba5bec..f71eebdc 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -62,6 +62,16 @@
int getNightMode();
/**
+ * Sets the dark mode for the given application. This setting is persisted and will override the
+ * system configuration for this application.
+ * 1 - notnight mode
+ * 2 - night mode
+ * 3 - automatic mode switching
+ * @throws RemoteException
+ */
+ void setApplicationNightMode(in int mode);
+
+ /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
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 c242fd4..77daf8d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1208,7 +1208,8 @@
* of a {@link BigPictureStyle} notification. This will replace a
* {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
*/
- public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
+ public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
+ "android.showBigPictureWhenCollapsed";
/**
* {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
@@ -1603,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;
@@ -2314,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 {}
@@ -7059,7 +7069,7 @@
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
- private boolean mPromotePicture;
+ private boolean mShowBigPictureWhenCollapsed;
public BigPictureStyle() {
}
@@ -7124,7 +7134,7 @@
*/
@NonNull
public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
- mPromotePicture = show;
+ mShowBigPictureWhenCollapsed = show;
return this;
}
@@ -7195,7 +7205,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (mPicture == null || !mPromotePicture) {
+ if (mPicture == null || !mShowBigPictureWhenCollapsed) {
return super.makeContentView(increasedHeight);
}
@@ -7225,7 +7235,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (mPicture == null || !mPromotePicture) {
+ if (mPicture == null || !mShowBigPictureWhenCollapsed) {
return super.makeHeadsUpContentView(increasedHeight);
}
@@ -7309,7 +7319,7 @@
extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
mPictureContentDescription);
}
- extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture);
+ extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
extras.putParcelable(EXTRA_PICTURE, mPicture);
}
@@ -7330,7 +7340,7 @@
extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
}
- mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE);
+ mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
@@ -9972,6 +9982,15 @@
* <p>The shortcut activity will be used when the bubble is expanded. This will display
* the shortcut activity in a floating window over the existing foreground activity.</p>
*
+ * <p>When the shortcut is displayed in a bubble, there will be an intent
+ * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+ * with {@code true}. You may check this in the onCreate of your activity via:
+ *
+ * <pre class="prettyprint">
+ * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+ * </pre>
+ * </p>
+ *
* <p>If the shortcut has not been published when the bubble notification is sent,
* no bubble will be produced. If the shortcut is deleted while the bubble is active,
* the bubble will be removed.</p>
@@ -10000,6 +10019,15 @@
* app content in a floating window over the existing foreground activity. The intent
* should point to a resizable activity. </p>
*
+ * <p>When the activity is displayed in a bubble, there will be an intent
+ * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+ * with {@code true}. You may check this in the onCreate of your activity via:
+ *
+ * <pre class="prettyprint">
+ * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+ * </pre>
+ * </p>
+ *
* @throws NullPointerException if intent is null.
* @throws NullPointerException if icon is null.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7404e53..c047fc2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -128,11 +128,13 @@
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IVpnManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
import android.net.TetheringManager;
+import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -167,9 +169,11 @@
import android.os.SystemConfigManager;
import android.os.SystemUpdateManager;
import android.os.SystemVibrator;
+import android.os.SystemVibratorManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibratorManager;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
@@ -382,6 +386,15 @@
ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE));
}});
+ registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
+ new CachedServiceFetcher<VpnManager>() {
+ @Override
+ public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
+ IVpnManager service = IVpnManager.Stub.asInterface(b);
+ return new VpnManager(ctx, service);
+ }});
+
registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class,
new CachedServiceFetcher<VcnManager>() {
@Override
@@ -699,6 +712,13 @@
}
});
+ registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
+ new CachedServiceFetcher<VibratorManager>() {
+ @Override
+ public VibratorManager createService(ContextImpl ctx) {
+ return new SystemVibratorManager(ctx);
+ }});
+
registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
new CachedServiceFetcher<Vibrator>() {
@Override
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/UiModeManager.java b/core/java/android/app/UiModeManager.java
index e1c262c..9b99ab8 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -477,11 +477,13 @@
* Changes to night mode take effect globally and will result in a configuration change
* (and potentially an Activity lifecycle event) being applied to all running apps.
* Developers interested in an app-local implementation of night mode should consider using
- * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} to manage the
- * -night qualifier locally.
+ * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
+ * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+ * backward compatible implementation.
*
* @param mode the night mode to set
* @see #getNightMode()
+ * @see #setApplicationNightMode(int)
*/
public void setNightMode(@NightMode int mode) {
if (mService != null) {
@@ -494,6 +496,44 @@
}
/**
+ * Sets and persist the night mode for this application.
+ * <p>
+ * The mode can be one of:
+ * <ul>
+ * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+ * {@code notnight} mode</li>
+ * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
+ * {@code night} mode</li>
+ * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
+ * {@code night} and {@code notnight} based on the custom time set (or default)</li>
+ * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
+ * {@code night} and {@code notnight} based on the device's current
+ * location and certain other sensors</li>
+ * </ul>
+ * <p>
+ * Changes to night mode take effect locally and will result in a configuration change
+ * (and potentially an Activity lifecycle event) being applied to this application. The mode
+ * is persisted for this application until it is either modified by the application, the
+ * user clears the data for the application, or this application is uninstalled.
+ * <p>
+ * Developers interested in a non-persistent app-local implementation of night mode should
+ * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+ * to manage the -night qualifier locally.
+ *
+ * @param mode the night mode to set
+ * @see #setNightMode(int)
+ */
+ public void setApplicationNightMode(@NightMode int mode) {
+ if (mService != null) {
+ try {
+ mService.setApplicationNightMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Returns the currently configured night mode.
* <p>
* May be one of:
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/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d175a66..4dbff0c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -16,6 +16,8 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.OperationSafetyReason;
+
import android.accounts.AccountManager;
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
@@ -35,6 +37,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.security.KeyChain;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -72,8 +75,8 @@
* </div>
*/
public class DeviceAdminReceiver extends BroadcastReceiver {
- private static String TAG = "DevicePolicy";
- private static boolean localLOGV = false;
+ private static final String TAG = "DevicePolicy";
+ private static final boolean LOCAL_LOGV = false;
/**
* This is the primary action that a device administrator must implement to be
@@ -509,6 +512,36 @@
public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
"android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
+ /**
+ * Broadcast action: notify the admin that the state of operations that can be unsafe because
+ * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra)
+ * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE}
+ * {@code boolean} extra).
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED =
+ "android.app.action.OPERATION_SAFETY_STATE_CHANGED";
+
+ /**
+ * An {@code int} extra specifying an {@link OperationSafetyReason}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_OPERATION_SAFETY_REASON =
+ "android.app.extra.OPERATION_SAFETY_REASON";
+
+ /**
+ * An {@code boolean} extra specifying whether an operation will fail due to a
+ * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are
+ * safe, while {@code false} means they're unsafe.
+ *
+ * @hide
+ */
+ public static final String EXTRA_OPERATION_SAFETY_STATE =
+ "android.app.extra.OPERATION_SAFETY_STATE";
+
private DevicePolicyManager mManager;
private ComponentName mWho;
@@ -1018,6 +1051,51 @@
}
/**
+ * Called to notify the state of operations that can be unsafe to execute has changed.
+ *
+ * <p><b>Note:/b> notice that the operation safety state might change between the time this
+ * callback is received and the operation's method on {@link DevicePolicyManager} is called, so
+ * calls to the latter could still throw a {@link UnsafeStateException} even when this method
+ * is called with {@code isSafe} as {@code true}
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param reason the reason an operation could be unsafe.
+ * @param isSafe whether the operation is safe to be executed.
+ */
+ public void onOperationSafetyStateChanged(@NonNull Context context,
+ @OperationSafetyReason int reason, boolean isSafe) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b",
+ DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+ }
+ }
+
+ private void onOperationSafetyStateChanged(Context context, Intent intent) {
+ if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON)
+ || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) {
+ return;
+ }
+
+ int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON,
+ DevicePolicyManager.OPERATION_SAFETY_REASON_NONE);
+ if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) {
+ Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason);
+ return;
+ }
+ boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE,
+ /* defaultValue=*/ false);
+
+ onOperationSafetyStateChanged(context, reason, isSafe);
+ }
+
+ private boolean hasRequiredExtra(Intent intent, String extra) {
+ if (intent.hasExtra(extra)) return true;
+
+ Log.wtf(TAG, "Missing '" + extra + "' on intent " + intent);
+ return false;
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -1025,6 +1103,9 @@
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId());
+ }
if (ACTION_PASSWORD_CHANGED.equals(action)) {
onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
@@ -1092,6 +1173,8 @@
} else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
onTransferAffiliatedProfileOwnershipComplete(context,
intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
+ onOperationSafetyStateChanged(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff41d1c8..05e9dcf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -206,7 +206,6 @@
* {@link android.os.Build.VERSION_CODES#N}</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
@@ -250,7 +249,6 @@
* <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* </ul>
*
* <p>If provisioning fails, the device returns to its previous state.
@@ -289,7 +287,6 @@
* <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li>
* </ul>
@@ -388,8 +385,6 @@
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
* <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
@@ -438,8 +433,6 @@
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
@@ -654,7 +647,10 @@
*
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or
* {@link #ACTION_PROVISION_MANAGED_DEVICE}.
+ *
+ * @deprecated Color customization is no longer supported in the provisioning flow.
*/
+ @Deprecated
public static final String EXTRA_PROVISIONING_MAIN_COLOR =
"android.app.extra.PROVISIONING_MAIN_COLOR";
@@ -905,8 +901,10 @@
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
* or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
+ * @deprecated This extra is no longer respected in the provisioning flow.
* @hide
*/
+ @Deprecated
@SystemApi
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL =
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -930,9 +928,11 @@
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
* or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
+ * @deprecated This extra is no longer respected in the provisioning flow.
* @hide
*/
@SystemApi
+ @Deprecated
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI =
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
@@ -1300,10 +1300,11 @@
* A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is
* organization-owned.
*
- * <p>Using this value will cause the admin app's {@link #ACTION_GET_PROVISIONING_MODE}
- * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
- * contain {@link #PROVISIONING_MODE_MANAGED_PROFILE} and {@link
- * #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
+ * <p>Using this value indicates the admin app can only be provisioned in either a
+ * fully-managed device or a corporate-owned work profile. This will cause the admin app's
+ * {@link #ACTION_GET_PROVISIONING_MODE} activity to have the {@link
+ * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
+ * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
*
* <p>Also, if this value is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
* will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
@@ -2922,33 +2923,61 @@
return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
}
- private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+ private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_";
/** @hide */
- @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
- UNSAFE_OPERATION_REASON_NONE,
- UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+ @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = {
+ OPERATION_SAFETY_REASON_NONE,
+ OPERATION_SAFETY_REASON_DRIVING_DISTRACTION
})
@Retention(RetentionPolicy.SOURCE)
- public static @interface UnsafeOperationReason {
+ public static @interface OperationSafetyReason {
}
/** @hide */
@TestApi
- public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+ public static final int OPERATION_SAFETY_REASON_NONE = -1;
/**
* Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
* the driver of the vehicle.
*/
- public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+ public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
/** @hide */
@NonNull
@TestApi
- public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+ public static String operationSafetyReasonToString(@OperationSafetyReason int reason) {
return DebugUtils.constantToString(DevicePolicyManager.class,
- PREFIX_UNSAFE_OPERATION_REASON, reason);
+ PREFIX_OPERATION_SAFETY_REASON, reason);
+ }
+
+ /** @hide */
+ public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) {
+ return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+ }
+
+ /**
+ * Checks if it's safe to run operations that can be affected by the given {@code reason}.
+ *
+ * <p><b>Note:/b> notice that the operation safety state might change between the time this
+ * method returns and the operation's method is called, so calls to the latter could still throw
+ * a {@link UnsafeStateException} even when this method returns {@code true}.
+ *
+ * @param reason currently, only supported reason is
+ * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
+ *
+ * @return whether it's safe to run operations that can be affected by the given {@code reason}.
+ */
+ // TODO(b/173541467): should it throw SecurityException if caller is not admin?
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ if (mService == null) return false;
+
+ try {
+ return mService.isSafeOperation(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** @hide */
@@ -11689,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:
@@ -11730,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
*/
@@ -11744,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");
@@ -11763,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.
*
@@ -11777,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.
@@ -11789,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
*/
@@ -11909,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.
*
@@ -13157,7 +13198,7 @@
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
public void setNextOperationSafety(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
if (mService != null) {
try {
mService.setNextOperationSafety(operation, reason);
@@ -13329,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/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index a0d2977..67f5c36 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.content.ComponentName;
import android.content.Intent;
import android.os.UserHandle;
@@ -255,4 +256,14 @@
* {@link #supportsResetOp(int)} is true.
*/
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+ /**
+ * Notifies the system that an unsafe operation reason has changed.
+ *
+ * @throws IllegalArgumentException if {@code checker} is not the same as set on
+ * {@code DevicePolicyManagerService}.
+ */
+ public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
+ @OperationSafetyReason int reason, boolean isSafe);
+
}
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index 6c6f2aa..17b74b1 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,7 +17,7 @@
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import com.android.internal.os.IResultReceiver;
@@ -31,15 +31,20 @@
/**
* Returns whether the given {@code operation} can be safely executed at the moment.
*/
- @UnsafeOperationReason
+ @OperationSafetyReason
int getUnsafeOperationReason(@DevicePolicyOperation int operation);
/**
+ * Return whether it's safe to run operations that can be affected by the given {@code reason}.
+ */
+ boolean isSafeOperation(@OperationSafetyReason int reason);
+
+ /**
* Returns a new exception for when the given {@code operation} cannot be safely executed.
*/
@NonNull
default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
return new UnsafeStateException(operation, reason);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 89f30cc..94388cf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -492,6 +492,7 @@
boolean canProfileOwnerResetPasswordWhenLocked(int userId);
void setNextOperationSafety(int operation, int reason);
+ boolean isSafeOperation(int reason);
String getEnrollmentSpecificId(String callerPackage);
void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
@@ -501,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/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 56eeb06..f1f6526 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,18 +15,20 @@
*/
package android.app.admin;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
* was not safe to be executed at that moment.
@@ -39,17 +41,15 @@
public final class UnsafeStateException extends IllegalStateException implements Parcelable {
private final @DevicePolicyOperation int mOperation;
- private final @UnsafeOperationReason int mReason;
+ private final @OperationSafetyReason int mReason;
/** @hide */
@TestApi
public UnsafeStateException(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
super();
- Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
- "invalid reason %d, must be %d (%s)", reason,
- UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
- unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
+ Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d",
+ reason);
mOperation = operation;
mReason = reason;
}
@@ -61,19 +61,20 @@
}
/**
- * Gets the reason the operation is unsafe.
+ * Gets the reasons the operation is unsafe.
*
* @return currently, only valid reason is
- * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+ * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
*/
- public @UnsafeOperationReason int getReason() {
- return mReason;
+ @NonNull
+ public List<Integer> getReasons() {
+ return Arrays.asList(mReason);
}
/** @hide */
@Override
public String getMessage() {
- return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+ return DevicePolicyManager.operationSafetyReasonToString(mReason);
}
@Override
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 7e232ac..85cfe83 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -401,7 +401,8 @@
* @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
- FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
+ FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
+ mOperationType);
if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
return;
}
@@ -624,7 +625,8 @@
if (includeMap == null || includeMap.size() == 0) {
// Do entire sub-tree for the provided token.
fullBackupFileTree(packageName, domainToken,
- FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
+ FullBackup.getBackupScheme(this, mOperationType)
+ .tokenToDirectoryPath(domainToken),
filterSet, traversalExcludeSet, data);
} else if (includeMap.get(domainToken) != null) {
// This will be null if the xml parsing didn't yield any rules for
@@ -795,7 +797,8 @@
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
- String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+ .tokenToDirectoryPath(domain);
if (domainPath == null) {
// Should never happen.
return;
@@ -911,7 +914,7 @@
return true;
}
- FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
+ FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
if (!bs.isFullBackupContentEnabled()) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
@@ -985,7 +988,8 @@
+ " domain=" + domain + " relpath=" + path + " mode=" + mode
+ " mtime=" + mtime);
- basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+ domain);
if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
mode = -1; // < 0 is a token to skip attempting a chmod()
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 742d05c..f7ed6f1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,9 @@
package android.app.backup;
+import static android.app.backup.BackupManager.OperationType;
+
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -41,6 +44,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -90,27 +94,61 @@
"fakeClientSideEncryption";
/**
+ * Identify {@link BackupScheme} object by package and operation type
+ * (see {@link OperationType}) it corresponds to.
+ */
+ private static class BackupSchemeId {
+ final String mPackageName;
+ @OperationType final int mOperationType;
+
+ BackupSchemeId(String packageName, @OperationType int operationType) {
+ mPackageName = packageName;
+ mOperationType = operationType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mOperationType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null || getClass() != object.getClass()) {
+ return false;
+ }
+ BackupSchemeId that = (BackupSchemeId) object;
+ return Objects.equals(mPackageName, that.mPackageName) &&
+ Objects.equals(mOperationType, that.mOperationType);
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, FullBackupDataOutput output);
- private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
- new ArrayMap<String, BackupScheme>();
+ private static final Map<BackupSchemeId, BackupScheme> kPackageBackupSchemeMap =
+ new ArrayMap<>();
- static synchronized BackupScheme getBackupScheme(Context context) {
+ static synchronized BackupScheme getBackupScheme(Context context,
+ @OperationType int operationType) {
+ BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
BackupScheme backupSchemeForPackage =
- kPackageBackupSchemeMap.get(context.getPackageName());
+ kPackageBackupSchemeMap.get(backupSchemeId);
if (backupSchemeForPackage == null) {
- backupSchemeForPackage = new BackupScheme(context);
- kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
+ backupSchemeForPackage = new BackupScheme(context, operationType);
+ kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
}
return backupSchemeForPackage;
}
public static BackupScheme getBackupSchemeForTest(Context context) {
- BackupScheme testing = new BackupScheme(context);
+ BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
testing.mExcludes = new ArraySet();
testing.mIncludes = new ArrayMap();
return testing;
@@ -236,6 +274,7 @@
private final static String TAG_EXCLUDE = "exclude";
final int mFullBackupContent;
+ @OperationType final int mOperationType;
final PackageManager mPackageManager;
final StorageManager mStorageManager;
final String mPackageName;
@@ -354,8 +393,9 @@
*/
ArraySet<PathWithRequiredFlags> mExcludes;
- BackupScheme(Context context) {
+ BackupScheme(Context context, @OperationType int operationType) {
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
+ mOperationType = operationType;
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
mPackageManager = context.getPackageManager();
mPackageName = context.getPackageName();
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/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d451599..e6fdc00 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -60,7 +60,7 @@
public void postExecute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): Use interface callback instead of actual implementation.
- ActivityClient.getInstance().activityResumed(token);
+ ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
}
@Override
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
new file mode 100644
index 0000000..5374984
--- /dev/null
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Transfer a splash screen view to an Activity.
+ * @hide
+ */
+public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
+
+ private SplashScreenViewParcelable mSplashScreenViewParcelable;
+ private @TransferRequest int mRequest;
+
+ @IntDef(value = {
+ ATTACH_TO,
+ HANDOVER_TO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransferRequest {}
+ // request client to attach the view on it.
+ public static final int ATTACH_TO = 0;
+ // tell client that you can handle the splash screen view.
+ public static final int HANDOVER_TO = 1;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull ActivityThread.ActivityClientRecord r,
+ PendingTransactionActions pendingActions) {
+ switch (mRequest) {
+ case ATTACH_TO:
+ client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
+ break;
+ case HANDOVER_TO:
+ client.handOverSplashScreenView(r);
+ break;
+ }
+ }
+
+ @Override
+ public void recycle() {
+ ObjectPool.recycle(this);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRequest);
+ dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+ }
+
+ private TransferSplashScreenViewStateItem() {}
+ private TransferSplashScreenViewStateItem(Parcel in) {
+ mRequest = in.readInt();
+ mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+ }
+
+ /** Obtain an instance initialized with provided params. */
+ public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
+ @Nullable SplashScreenViewParcelable parcelable) {
+ TransferSplashScreenViewStateItem instance =
+ ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
+ if (instance == null) {
+ instance = new TransferSplashScreenViewStateItem();
+ }
+ instance.mRequest = state;
+ instance.mSplashScreenViewParcelable = parcelable;
+
+ return instance;
+ }
+
+ public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
+ new Creator<TransferSplashScreenViewStateItem>() {
+ public TransferSplashScreenViewStateItem createFromParcel(Parcel in) {
+ return new TransferSplashScreenViewStateItem(in);
+ }
+
+ public TransferSplashScreenViewStateItem[] newArray(int size) {
+ return new TransferSplashScreenViewStateItem[size];
+ }
+ };
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
rename to core/java/android/app/time/ExternalTimeSuggestion.aidl
index 14d57bf..07a0fbb 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
parcelable ExternalTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
similarity index 99%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.java
rename to core/java/android/app/time/ExternalTimeSuggestion.java
index 7ad303a..b566eab 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 4626543..c4546be 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,7 +16,7 @@
package android.app.timedetector;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index df8d797..76f3785 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.app.time.ExternalTimeSuggestion;
import android.content.Context;
import android.os.SystemClock;
import android.os.TimestampedValue;
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index f80869f..ef818ef 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,6 +17,7 @@
package android.app.timedetector;
import android.annotation.NonNull;
+import android.app.time.ExternalTimeSuggestion;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
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/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/ClipData.java b/core/java/android/content/ClipData.java
index 0188637..f3ecbf6 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -740,6 +740,7 @@
mIcon = null;
mItems = new ArrayList<Item>();
mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
}
/**
@@ -756,6 +757,7 @@
mIcon = null;
mItems = new ArrayList<Item>();
mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
}
/**
@@ -914,6 +916,9 @@
throw new NullPointerException("item is null");
}
mItems.add(item);
+ if (mItems.size() == 1) {
+ mClipDescription.setIsStyledText(isStyledText());
+ }
}
/**
@@ -1049,6 +1054,20 @@
}
}
+ private boolean isStyledText() {
+ if (mItems.isEmpty()) {
+ return false;
+ }
+ final CharSequence text = mItems.get(0).getText();
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ if (TextUtils.hasStyleSpan(spanned)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index e3395e2..d48f832 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -120,6 +120,7 @@
private final ArrayList<String> mMimeTypes;
private PersistableBundle mExtras;
private long mTimeStamp;
+ private boolean mIsStyledText;
/**
* Create a new clip.
@@ -325,6 +326,26 @@
}
}
+ /**
+ * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e.
+ * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link
+ * android.text.style.ParagraphStyle ParagraphStyle}, or {@link
+ * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if
+ * there is no associated clip data.
+ */
+ public boolean isStyledText() {
+ return mIsStyledText;
+ }
+
+ /**
+ * Sets whether the associated {@link ClipData} contains styled text in its first item. This
+ * should be called when this description is associated with clip data or when the first item
+ * is added to the associated clip data.
+ */
+ void setIsStyledText(boolean isStyledText) {
+ mIsStyledText = isStyledText;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -429,6 +450,7 @@
dest.writeStringList(mMimeTypes);
dest.writePersistableBundle(mExtras);
dest.writeLong(mTimeStamp);
+ dest.writeBoolean(mIsStyledText);
}
ClipDescription(Parcel in) {
@@ -436,6 +458,7 @@
mMimeTypes = in.createStringArrayList();
mExtras = in.readPersistableBundle();
mTimeStamp = in.readLong();
+ mIsStyledText = in.readBoolean();
}
public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a402b2..24b7629 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;
/**
@@ -3479,6 +3480,7 @@
STORAGE_STATS_SERVICE,
WALLPAPER_SERVICE,
TIME_ZONE_RULES_MANAGER_SERVICE,
+ VIBRATOR_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
@@ -3625,9 +3627,11 @@
* (e.g., GPS) updates.
* <dt> {@link #SEARCH_SERVICE} ("search")
* <dd> A {@link android.app.SearchManager} for handling search.
+ * <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+ * <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+ * with individual ones and playing synchronized effects on multiple vibrators.
* <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
- * <dd> A {@link android.os.Vibrator} for interacting with the vibrator
- * hardware.
+ * <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
* <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
* <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
* handling management of network connections.
@@ -3707,6 +3711,8 @@
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
* @see android.os.storage.StorageManager
+ * @see #VIBRATOR_MANAGER_SERVICE
+ * @see android.os.VibratorManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
@@ -4034,8 +4040,19 @@
public static final String WALLPAPER_SERVICE = "wallpaper";
/**
- * Use with {@link #getSystemService(String)} to retrieve a {@link
- * android.os.Vibrator} for interacting with the vibration hardware.
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+ * for accessing the device vibrators, interacting with individual ones and playing synchronized
+ * effects on multiple vibrators.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.VibratorManager
+ */
+ @SuppressLint("ServiceName")
+ public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+ * interacting with the vibration hardware.
*
* @see #getSystemService(String)
* @see android.os.Vibrator
@@ -5709,6 +5726,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
@@ -5739,6 +5782,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
@@ -5759,6 +5828,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/Intent.java b/core/java/android/content/Intent.java
index 30b2404..4abd8cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5718,7 +5718,7 @@
public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
/**
- * Used in the extra field in the remote intent. It's astring token passed with the
+ * Used in the extra field in the remote intent. It's a string token passed with the
* remote intent.
*/
public static final String EXTRA_REMOTE_INTENT_TOKEN =
@@ -6062,6 +6062,16 @@
*/
public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
+ /**
+ * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or
+ * pending intent provided for the bubble. If the extra is not present or false, then it is not
+ * bubbled.
+ *
+ * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+ * @see android.app.Notification.BubbleMetadata.Builder#Builder(String)
+ */
+ public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 85549d8..ebe202b 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -273,7 +273,7 @@
text, 0, null, disabledMessage, 0, null,
categoriesSet, intents, rank, extras,
getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
- disabledReason, persons, locusId);
+ disabledReason, persons, locusId, 0);
}
/** @hide */
diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java
index e8fb241..4d79936 100644
--- a/core/java/android/content/pm/DataLoaderManager.java
+++ b/core/java/android/content/pm/DataLoaderManager.java
@@ -41,6 +41,7 @@
* @param dataLoaderId ID for the new data loader binder service.
* @param params DataLoaderParamsParcel object that contains data loader params, including
* its package name, class name, and additional parameters.
+ * @param bindDelayMs introduce a delay before actual bind in case we want to avoid busylooping
* @param listener Callback for the data loader service to report status back to the
* caller.
* @return false if 1) target ID collides with a data loader that is already bound to data
@@ -48,9 +49,9 @@
* or 4) fails to bind to the specified data loader service, otherwise return true.
*/
public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
- @NonNull IDataLoaderStatusListener listener) {
+ long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
try {
- return mService.bindToDataLoader(dataLoaderId, params, listener);
+ return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl
index 93b3de7..dda4d36 100644
--- a/core/java/android/content/pm/IDataLoaderManager.aidl
+++ b/core/java/android/content/pm/IDataLoaderManager.aidl
@@ -23,7 +23,7 @@
/** @hide */
interface IDataLoaderManager {
- boolean bindToDataLoader(int id, in DataLoaderParamsParcel params,
+ boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs,
IDataLoaderStatusListener listener);
IDataLoader getDataLoader(int dataLoaderId);
void unbindFromDataLoader(int dataLoaderId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a46876e..7fe2a41 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -808,4 +808,6 @@
PackageManager.Property getProperty(String propertyName, String packageName, String className);
ParceledListSlice queryProperty(String propertyName, int componentType);
+
+ void setKeepUninstalledPackages(in List<String> packageList);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b95b991b..a3c3500 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -3592,9 +3592,12 @@
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
* the requisite kernel support to support incremental delivery aka Incremental FileSystem.
*
- * @see IncrementalManager#isEnabled
+ * @see IncrementalManager#isFeatureEnabled
* @hide
+ *
+ * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead.
*/
+ @Deprecated
@SystemApi
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_INCREMENTAL_DELIVERY =
@@ -3602,6 +3605,20 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * feature not present - IncFs is not present on the device.
+ * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+ * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+ *
+ * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2()
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION =
+ "android.software.incremental_delivery_version";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device has tuner hardware to support tuner operations.
*
* <p>This feature implies that the device has the tuner HAL implementation.
@@ -9282,4 +9299,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Set a list of apps to keep around as APKs even if no user has currently installed it.
+ * @param packageList List of package names to keep cached.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+ @TestApi
+ public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+ try {
+ ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d17..bf8d1f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6151,6 +6151,56 @@
}
/**
+ * Returns whether this instance is currently signed, or has ever been signed, with a
+ * signing certificate from the provided {@link Set} of {@code certDigests}.
+ *
+ * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+ * of each trusted certificate with the digest characters in upper case. If this instance
+ * has multiple signers then all signers must be in the provided {@code Set}. If this
+ * instance has a signing lineage then this method will return true if any of the previous
+ * signers in the lineage match one of the entries in the {@code Set}.
+ */
+ public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+ if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+ return false;
+ }
+ // If an app is signed by multiple signers then all of the signers must be in the Set.
+ if (signatures.length > 1) {
+ // If the Set has less elements than the number of signatures then immediately
+ // return false as there's no way to satisfy the requirement of all signatures being
+ // in the Set.
+ if (certDigests.size() < signatures.length) {
+ return false;
+ }
+ for (Signature signature : signatures) {
+ String signatureDigest = PackageUtils.computeSha256Digest(
+ signature.toByteArray());
+ if (!certDigests.contains(signatureDigest)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ if (hasPastSigningCertificates()) {
+ // The last element in the pastSigningCertificates array is the current signer;
+ // since that was verified above just check all the signers in the lineage.
+ for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+ signatureDigest = PackageUtils.computeSha256Digest(
+ pastSigningCertificates[i].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the SigningDetails with a descendant (or same) signer after verifying the
* descendant has the same, a superset, or a subset of the lineage of the ancestor.
*
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8..0e70a3e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -28,8 +28,12 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
/**
* Information you can retrieve about a particular security permission
@@ -278,6 +282,15 @@
@SystemApi
public static final int PROTECTION_FLAG_ROLE = 0x4000000;
+ /**
+ * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+ * of {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -303,6 +316,7 @@
PROTECTION_FLAG_RETAIL_DEMO,
PROTECTION_FLAG_RECENTS,
PROTECTION_FLAG_ROLE,
+ PROTECTION_FLAG_KNOWN_SIGNER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -466,6 +480,17 @@
*/
public @Nullable CharSequence nonLocalizedDescription;
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+ /**
+ * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+ * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+ * if the app is signed by any of these certificates.
+ *
+ * @hide
+ */
+ public @Nullable Set<String> knownCerts;
+
/** @hide */
public static int fixProtectionLevel(int level) {
if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -570,6 +595,9 @@
if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
protLevel.append("|role");
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+ protLevel.append("|knownSigner");
+ }
return protLevel.toString();
}
@@ -665,6 +693,7 @@
dest.writeInt(descriptionRes);
dest.writeInt(requestRes);
TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ sForStringSet.parcel(knownCerts, dest, parcelableFlags);
}
/** @hide */
@@ -730,5 +759,6 @@
descriptionRes = source.readInt();
requestRes = source.readInt();
nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ knownCerts = sForStringSet.unparcel(source);
}
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 522f4ca..ce0547f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -434,6 +434,8 @@
private int mDisabledReason;
+ private int mStartingThemeResId;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -462,6 +464,7 @@
mLocusId = b.mLocusId;
updateTimestamp();
+ mStartingThemeResId = b.mStartingThemeResId;
}
/**
@@ -608,6 +611,7 @@
// Set this bit.
mFlags |= FLAG_KEY_FIELDS_ONLY;
}
+ mStartingThemeResId = source.mStartingThemeResId;
}
/**
@@ -931,6 +935,9 @@
if (source.mLocusId != null) {
mLocusId = source.mLocusId;
}
+ if (source.mStartingThemeResId != 0) {
+ mStartingThemeResId = source.mStartingThemeResId;
+ }
}
/**
@@ -1000,6 +1007,8 @@
private LocusId mLocusId;
+ private int mStartingThemeResId;
+
/**
* Old style constructor.
* @hide
@@ -1102,6 +1111,15 @@
}
/**
+ * Sets a theme resource id for the splash screen.
+ */
+ @NonNull
+ public Builder setStartingTheme(int themeResId) {
+ mStartingThemeResId = themeResId;
+ return this;
+ }
+
+ /**
* @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests
* use it.)
*/
@@ -1420,6 +1438,14 @@
return mIcon;
}
+ /**
+ * Returns the theme resource id used for the splash screen.
+ * @hide
+ */
+ public int getStartingThemeResId() {
+ return mStartingThemeResId;
+ }
+
/** @hide -- old signature, the internal code still uses it. */
@Nullable
@Deprecated
@@ -2138,6 +2164,7 @@
mPersons = source.readParcelableArray(cl, Person.class);
mLocusId = source.readParcelable(cl);
mIconUri = source.readString8();
+ mStartingThemeResId = source.readInt();
}
@Override
@@ -2189,6 +2216,7 @@
dest.writeParcelableArray(mPersons, flags);
dest.writeParcelable(mLocusId, flags);
dest.writeString8(mIconUri);
+ dest.writeInt(mStartingThemeResId);
}
public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2345,6 +2373,12 @@
sb.append("disabledReason=");
sb.append(getDisabledReasonDebugString(mDisabledReason));
+ if (mStartingThemeResId != 0) {
+ addIndentOrComma(sb, indent);
+ sb.append("SplashScreenThemeResId=");
+ sb.append(Integer.toHexString(mStartingThemeResId));
+ }
+
addIndentOrComma(sb, indent);
sb.append("categories=");
@@ -2430,7 +2464,7 @@
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
- int disabledReason, Person[] persons, LocusId locusId) {
+ int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -2459,5 +2493,6 @@
mDisabledReason = disabledReason;
mPersons = persons;
mLocusId = locusId;
+ mStartingThemeResId = startingThemeResId;
}
}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index c62767e..233abf3 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -71,6 +71,13 @@
public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
+ /**
+ * Get the theme res ID of the starting window, it can be 0 if not specified.
+ */
+ public abstract int getShortcutStartingThemeResId(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
+ int userId);
+
public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
@NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
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/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904..9a84ded 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -655,6 +655,7 @@
pi.protectionLevel = p.getProtectionLevel();
pi.descriptionRes = p.getDescriptionRes();
pi.flags = p.getFlags();
+ pi.knownCerts = p.getKnownCerts();
if ((flags & PackageManager.GET_META_DATA) == 0) {
return pi;
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1..37e0e87 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -21,14 +21,22 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.util.Locale;
+import java.util.Set;
/** @hide */
public class ParsedPermission extends ParsedComponent {
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
@Nullable
String backgroundPermission;
@Nullable
@@ -39,6 +47,8 @@
boolean tree;
@Nullable
private ParsedPermissionGroup parsedPermissionGroup;
+ @Nullable
+ Set<String> knownCerts;
@VisibleForTesting
public ParsedPermission() {
@@ -81,6 +91,23 @@
return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
}
+ public @Nullable Set<String> getKnownCerts() {
+ return knownCerts;
+ }
+
+ protected void setKnownCert(String knownCert) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+
+ protected void setKnownCerts(String[] knownCerts) {
+ this.knownCerts = new ArraySet<>();
+ for (String knownCert : knownCerts) {
+ this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
public int calculateFootprint() {
int size = getName().length();
if (getNonLocalizedLabel() != null) {
@@ -109,6 +136,7 @@
dest.writeInt(this.protectionLevel);
dest.writeBoolean(this.tree);
dest.writeParcelable(this.parsedPermissionGroup, flags);
+ sForStringSet.parcel(knownCerts, dest, flags);
}
protected ParsedPermission(Parcel in) {
@@ -121,6 +149,7 @@
this.protectionLevel = in.readInt();
this.tree = in.readBoolean();
this.parsedPermissionGroup = in.readParcelable(boot);
+ this.knownCerts = sForStringSet.unparcel(in);
}
public static final Parcelable.Creator<ParsedPermission> CREATOR =
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..8afa70e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -90,6 +90,38 @@
permission.flags = sa.getInt(
R.styleable.AndroidManifestPermission_permissionFlags, 0);
+ final int knownCertsResource = sa.getResourceId(
+ R.styleable.AndroidManifestPermission_knownCerts, 0);
+ if (knownCertsResource != 0) {
+ // The knownCerts attribute supports both a string array resource as well as a
+ // string resource for the case where the permission should only be granted to a
+ // single known signer.
+ final String resourceType = res.getResourceTypeName(knownCertsResource);
+ if (resourceType.equals("array")) {
+ final String[] knownCerts = res.getStringArray(knownCertsResource);
+ if (knownCerts != null) {
+ permission.setKnownCerts(knownCerts);
+ }
+ } else {
+ final String knownCert = res.getString(knownCertsResource);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+ if (permission.knownCerts == null) {
+ Slog.w(TAG, packageName + " defines a knownSigner permission but"
+ + " the provided knownCerts resource is null");
+ }
+ } else {
+ // If the knownCerts resource ID is null check if the app specified a string
+ // value for the attribute representing a single trusted signer.
+ final String knownCert = sa.getString(
+ R.styleable.AndroidManifestPermission_knownCerts);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+
// For now only platform runtime permissions can be restricted
if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cde7b2a..d302b0a 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -3,7 +3,6 @@
toddke@android.com
toddke@google.com
patb@google.com
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
zhanghai@google.com
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/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index abf694f..bbde8b1 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -252,9 +252,10 @@
}
if (overrideScale != 1.0f) {
- applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
applicationScale = overrideScale;
applicationInvertedScale = 1.0f / overrideScale;
+ applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+ * applicationInvertedScale) + .5f);
compatFlags |= HAS_OVERRIDE_SCALING;
} else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
@@ -519,10 +520,6 @@
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
- inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
- inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
- inoutConfig.smallestScreenWidthDp =
- (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 08b1e24..5f5697a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -236,7 +236,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext,
+ return new BiometricTestSession(mContext, sensorId,
mService.createTestSession(sensorId, mContext.getOpPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 2b68989..1c35608 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -25,6 +25,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Log;
/**
* Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -33,7 +34,10 @@
*/
@TestApi
public class BiometricTestSession implements AutoCloseable {
+ private static final String TAG = "BiometricTestSession";
+
private final Context mContext;
+ private final int mSensorId;
private final ITestSession mTestSession;
// Keep track of users that were tested, which need to be cleaned up when finishing.
@@ -42,8 +46,10 @@
/**
* @hide
*/
- public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+ public BiometricTestSession(@NonNull Context context, int sensorId,
+ @NonNull ITestSession testSession) {
mContext = context;
+ mSensorId = sensorId;
mTestSession = testSession;
mTestedUsers = new ArraySet<>();
setTestHalEnabled(true);
@@ -61,6 +67,7 @@
@RequiresPermission(TEST_BIOMETRIC)
private void setTestHalEnabled(boolean enabled) {
try {
+ Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
mTestSession.setTestHalEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8fe7158..8451ded 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1233,6 +1233,8 @@
int sequenceId) {
synchronized (mInterfaceLock) {
if (mInternalRepeatingRequestEnabled) {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
resumeInternalRepeatingRequest(true);
}
}
@@ -1263,7 +1265,12 @@
mRequestUpdatedNeeded = false;
resumeInternalRepeatingRequest(false);
} else if (mInternalRepeatingRequestEnabled) {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
resumeInternalRepeatingRequest(true);
+ } else {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
}
}
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/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index f175e7b..2d4b2cc 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -42,6 +42,12 @@
*/
public static final int INVALID_DEVICE_STATE = -1;
+ /** The minimum allowed device state identifier. */
+ public static final int MINIMUM_DEVICE_STATE = 0;
+
+ /** The maximum allowed device state identifier. */
+ public static final int MAXIMUM_DEVICE_STATE = 255;
+
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index f3da6a9..886a8c1 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -833,67 +833,6 @@
}
/**
- * @hide
- */
- public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
- switch (acquireInfo) {
- case FACE_ACQUIRED_GOOD:
- return null;
- case FACE_ACQUIRED_INSUFFICIENT:
- return context.getString(R.string.face_acquired_insufficient);
- case FACE_ACQUIRED_TOO_BRIGHT:
- return context.getString(R.string.face_acquired_too_bright);
- case FACE_ACQUIRED_TOO_DARK:
- return context.getString(R.string.face_acquired_too_dark);
- case FACE_ACQUIRED_TOO_CLOSE:
- return context.getString(R.string.face_acquired_too_close);
- case FACE_ACQUIRED_TOO_FAR:
- return context.getString(R.string.face_acquired_too_far);
- case FACE_ACQUIRED_TOO_HIGH:
- return context.getString(R.string.face_acquired_too_high);
- case FACE_ACQUIRED_TOO_LOW:
- return context.getString(R.string.face_acquired_too_low);
- case FACE_ACQUIRED_TOO_RIGHT:
- return context.getString(R.string.face_acquired_too_right);
- case FACE_ACQUIRED_TOO_LEFT:
- return context.getString(R.string.face_acquired_too_left);
- case FACE_ACQUIRED_POOR_GAZE:
- return context.getString(R.string.face_acquired_poor_gaze);
- case FACE_ACQUIRED_NOT_DETECTED:
- return context.getString(R.string.face_acquired_not_detected);
- case FACE_ACQUIRED_TOO_MUCH_MOTION:
- return context.getString(R.string.face_acquired_too_much_motion);
- case FACE_ACQUIRED_RECALIBRATE:
- return context.getString(R.string.face_acquired_recalibrate);
- case FACE_ACQUIRED_TOO_DIFFERENT:
- return context.getString(R.string.face_acquired_too_different);
- case FACE_ACQUIRED_TOO_SIMILAR:
- return context.getString(R.string.face_acquired_too_similar);
- case FACE_ACQUIRED_PAN_TOO_EXTREME:
- return context.getString(R.string.face_acquired_pan_too_extreme);
- case FACE_ACQUIRED_TILT_TOO_EXTREME:
- return context.getString(R.string.face_acquired_tilt_too_extreme);
- case FACE_ACQUIRED_ROLL_TOO_EXTREME:
- return context.getString(R.string.face_acquired_roll_too_extreme);
- case FACE_ACQUIRED_FACE_OBSCURED:
- return context.getString(R.string.face_acquired_obscured);
- case FACE_ACQUIRED_START:
- return null;
- case FACE_ACQUIRED_SENSOR_DIRTY:
- return context.getString(R.string.face_acquired_sensor_dirty);
- case FACE_ACQUIRED_VENDOR: {
- String[] msgArray = context.getResources().getStringArray(
- R.array.face_acquired_vendor);
- if (vendorCode < msgArray.length) {
- return msgArray[vendorCode];
- }
- }
- }
- Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
- return null;
- }
-
- /**
* Used so BiometricPrompt can map the face ones onto existing public constants.
* @hide
*/
@@ -1387,7 +1326,7 @@
final int acquireInfo = frame.getData().getAcquiredInfo();
final int vendorCode = frame.getData().getVendorCode();
final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+ final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
// Ensure that only non-null help messages are sent.
@@ -1405,7 +1344,7 @@
final int acquireInfo = frame.getData().getAcquiredInfo();
final int vendorCode = frame.getData().getVendorCode();
final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+ final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
}
}
@@ -1415,4 +1354,124 @@
? vendorCode + FACE_ACQUIRED_VENDOR_BASE
: acquireInfo;
}
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static String getAuthHelpMessage(Context context, int acquireInfo, int vendorCode) {
+ switch (acquireInfo) {
+ // No help message is needed for a good capture.
+ case FACE_ACQUIRED_GOOD:
+ case FACE_ACQUIRED_START:
+ return null;
+
+ // Consolidate positional feedback to reduce noise during authentication.
+ case FACE_ACQUIRED_NOT_DETECTED:
+ case FACE_ACQUIRED_TOO_CLOSE:
+ case FACE_ACQUIRED_TOO_FAR:
+ case FACE_ACQUIRED_TOO_HIGH:
+ case FACE_ACQUIRED_TOO_LOW:
+ case FACE_ACQUIRED_TOO_RIGHT:
+ case FACE_ACQUIRED_TOO_LEFT:
+ case FACE_ACQUIRED_POOR_GAZE:
+ case FACE_ACQUIRED_PAN_TOO_EXTREME:
+ case FACE_ACQUIRED_TILT_TOO_EXTREME:
+ case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_not_detected);
+
+ // Provide more detailed feedback for other soft errors.
+ case FACE_ACQUIRED_INSUFFICIENT:
+ return context.getString(R.string.face_acquired_insufficient);
+ case FACE_ACQUIRED_TOO_BRIGHT:
+ return context.getString(R.string.face_acquired_too_bright);
+ case FACE_ACQUIRED_TOO_DARK:
+ return context.getString(R.string.face_acquired_too_dark);
+ case FACE_ACQUIRED_TOO_MUCH_MOTION:
+ return context.getString(R.string.face_acquired_too_much_motion);
+ case FACE_ACQUIRED_RECALIBRATE:
+ return context.getString(R.string.face_acquired_recalibrate);
+ case FACE_ACQUIRED_TOO_DIFFERENT:
+ return context.getString(R.string.face_acquired_too_different);
+ case FACE_ACQUIRED_TOO_SIMILAR:
+ return context.getString(R.string.face_acquired_too_similar);
+ case FACE_ACQUIRED_FACE_OBSCURED:
+ return context.getString(R.string.face_acquired_obscured);
+ case FACE_ACQUIRED_SENSOR_DIRTY:
+ return context.getString(R.string.face_acquired_sensor_dirty);
+
+ // Find and return the appropriate vendor-specific message.
+ case FACE_ACQUIRED_VENDOR: {
+ String[] msgArray = context.getResources().getStringArray(
+ R.array.face_acquired_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+
+ Slog.w(TAG, "Unknown authentication acquired message: " + acquireInfo + ", " + vendorCode);
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static String getEnrollHelpMessage(Context context, int acquireInfo, int vendorCode) {
+ switch (acquireInfo) {
+ case FACE_ACQUIRED_GOOD:
+ case FACE_ACQUIRED_START:
+ return null;
+ case FACE_ACQUIRED_INSUFFICIENT:
+ return context.getString(R.string.face_acquired_insufficient);
+ case FACE_ACQUIRED_TOO_BRIGHT:
+ return context.getString(R.string.face_acquired_too_bright);
+ case FACE_ACQUIRED_TOO_DARK:
+ return context.getString(R.string.face_acquired_too_dark);
+ case FACE_ACQUIRED_TOO_CLOSE:
+ return context.getString(R.string.face_acquired_too_close);
+ case FACE_ACQUIRED_TOO_FAR:
+ return context.getString(R.string.face_acquired_too_far);
+ case FACE_ACQUIRED_TOO_HIGH:
+ return context.getString(R.string.face_acquired_too_high);
+ case FACE_ACQUIRED_TOO_LOW:
+ return context.getString(R.string.face_acquired_too_low);
+ case FACE_ACQUIRED_TOO_RIGHT:
+ return context.getString(R.string.face_acquired_too_right);
+ case FACE_ACQUIRED_TOO_LEFT:
+ return context.getString(R.string.face_acquired_too_left);
+ case FACE_ACQUIRED_POOR_GAZE:
+ return context.getString(R.string.face_acquired_poor_gaze);
+ case FACE_ACQUIRED_NOT_DETECTED:
+ return context.getString(R.string.face_acquired_not_detected);
+ case FACE_ACQUIRED_TOO_MUCH_MOTION:
+ return context.getString(R.string.face_acquired_too_much_motion);
+ case FACE_ACQUIRED_RECALIBRATE:
+ return context.getString(R.string.face_acquired_recalibrate);
+ case FACE_ACQUIRED_TOO_DIFFERENT:
+ return context.getString(R.string.face_acquired_too_different);
+ case FACE_ACQUIRED_TOO_SIMILAR:
+ return context.getString(R.string.face_acquired_too_similar);
+ case FACE_ACQUIRED_PAN_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_pan_too_extreme);
+ case FACE_ACQUIRED_TILT_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_tilt_too_extreme);
+ case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_roll_too_extreme);
+ case FACE_ACQUIRED_FACE_OBSCURED:
+ return context.getString(R.string.face_acquired_obscured);
+ case FACE_ACQUIRED_SENSOR_DIRTY:
+ return context.getString(R.string.face_acquired_sensor_dirty);
+ case FACE_ACQUIRED_VENDOR: {
+ String[] msgArray = context.getResources().getStringArray(
+ R.array.face_acquired_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+ Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
+ return null;
+ }
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 188a2a4..a614ebf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -153,7 +153,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext,
+ return new BiometricTestSession(mContext, sensorId,
mService.createTestSession(sensorId, mContext.getOpPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index f4d8a65..a4817ae 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -74,6 +74,11 @@
}
@Override
+ public int getId() {
+ return mVibratorId;
+ }
+
+ @Override
public boolean hasVibrator() {
return true;
}
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index a381b02..d843407 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -16,9 +16,12 @@
package android.hardware.input;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.CombinedVibrationEffect;
import android.os.NullVibrator;
+import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.util.SparseArray;
@@ -86,6 +89,7 @@
}
}
+ @NonNull
@Override
public int[] getVibratorIds() {
synchronized (mVibrators) {
@@ -97,6 +101,7 @@
}
}
+ @NonNull
@Override
public Vibrator getVibrator(int vibratorId) {
synchronized (mVibrators) {
@@ -107,6 +112,7 @@
return NullVibrator.getInstance();
}
+ @NonNull
@Override
public Vibrator getDefaultVibrator() {
// Returns vibrator ID 0
@@ -119,7 +125,13 @@
}
@Override
- public void vibrate(CombinedVibrationEffect effect) {
+ public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes) {
mInputManager.vibrate(mDeviceId, effect, mToken);
}
+
+ @Override
+ public void cancel() {
+ mInputManager.cancelVibrate(mDeviceId, mToken);
+ }
}
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/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f2b39d..8f5c2a0 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 175220
-moltmann@google.com
badhri@google.com
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/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d6774d4..933256a 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -58,6 +58,9 @@
in LinkAddress localAddr,
in String callingPackage);
+ void setNetworkForTunnelInterface(
+ int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
void deleteTunnelInterface(int resourceId, in String callingPackage);
IpSecTransformResponse createTransform(
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
index 14d57bf..7979afc 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
@@ -1,11 +1,12 @@
-/*
+/**
+ *
* 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +15,9 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.net;
-parcelable ExternalTimeSuggestion;
+/** @hide */
+oneway interface IOnSetOemNetworkPreferenceListener {
+ void onComplete();
+}
diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl
new file mode 100644
index 0000000..271efe4
--- /dev/null
+++ b/core/java/android/net/IVpnManager.aidl
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.Network;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+
+/**
+ * Interface that manages VPNs.
+ */
+/** {@hide} */
+interface IVpnManager {
+ /** VpnService APIs */
+ boolean prepareVpn(String oldPackage, String newPackage, int userId);
+ void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
+ ParcelFileDescriptor establishVpn(in VpnConfig config);
+ boolean addVpnAddress(String address, int prefixLength);
+ boolean removeVpnAddress(String address, int prefixLength);
+ boolean setUnderlyingNetworksForVpn(in Network[] networks);
+
+ /** VpnManager APIs */
+ boolean provisionVpnProfile(in VpnProfile profile, String packageName);
+ void deleteVpnProfile(String packageName);
+ void startVpnProfile(String packageName);
+ void stopVpnProfile(String packageName);
+
+ /** Always-on VPN APIs */
+ boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
+ boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+ in List<String> lockdownAllowlist);
+ String getAlwaysOnVpnPackage(int userId);
+ boolean isVpnLockdownEnabled(int userId);
+ List<String> getVpnLockdownAllowlist(int userId);
+ boolean isCallerCurrentAlwaysOnVpnApp();
+ boolean isCallerCurrentAlwaysOnVpnLockdownApp();
+
+ /** Legacy VPN APIs */
+ void startLegacyVpn(in VpnProfile profile);
+ LegacyVpnInfo getLegacyVpnInfo(int userId);
+ boolean updateLockdownVpn();
+
+ /** General system APIs */
+ VpnConfig getVpnConfig(int userId);
+ void factoryReset();
+}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 70bca30..98acd98 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -782,6 +782,42 @@
}
}
+ /**
+ * Update the underlying network for this IpSecTunnelInterface.
+ *
+ * <p>This new underlying network will be used for all transforms applied AFTER this call is
+ * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+ * this tunnel interface, traffic will still use the old SA, and be routed on the old
+ * underlying network.
+ *
+ * <p>To migrate IPsec tunnel mode traffic, a caller should:
+ *
+ * <ol>
+ * <li>Update the IpSecTunnelInterface’s underlying network.
+ * <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+ * IpSecTunnelInterface.
+ * </ol>
+ *
+ * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+ * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
+ * this method will throw an {@link IllegalArgumentException}.
+ */
+ // TODO: b/169171001 Update the documentation when transform migration is supported.
+ // The purpose of making updating network and applying transforms separate is to leave open
+ // the possibility to support lossless migration procedures. To do that, Android platform
+ // will need to support multiple inbound tunnel mode transforms, just like it can support
+ // multiple transport mode transforms.
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+ try {
+ mService.setNetworkForTunnelInterface(
+ mResourceId, underlyingNetwork, mOpPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
@NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
@NonNull Network underlyingNetwork)
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3e6237d..6353a25 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -55,6 +56,7 @@
*
* @hide
*/
+@TestApi
@SystemService(Context.NETWORK_POLICY_SERVICE)
public class NetworkPolicyManager {
@@ -125,6 +127,7 @@
public static final int RULE_REJECT_ALL = 1 << 6;
/**
* Reject traffic on all networks for restricted networking mode.
+ * @hide
*/
public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10;
@@ -351,6 +354,7 @@
}
/** @hide */
+ @TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setRestrictBackground(boolean restrictBackground) {
try {
@@ -361,6 +365,7 @@
}
/** @hide */
+ @TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean getRestrictBackground() {
try {
@@ -506,6 +511,8 @@
/**
* Get multipath preference for the given network.
+ *
+ * @hide
*/
public int getMultipathPreference(Network network) {
try {
@@ -624,7 +631,9 @@
}
/** @hide */
- public static String resolveNetworkId(WifiConfiguration config) {
+ @TestApi
+ @NonNull
+ public static String resolveNetworkId(@NonNull WifiConfiguration config) {
return WifiInfo.sanitizeSsid(config.isPasspoint()
? config.providerFriendlyName : config.SSID);
}
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index 5e56164..b403455 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcelable;
@@ -29,11 +30,12 @@
import java.util.Objects;
/** @hide */
+@SystemApi
public final class OemNetworkPreferences implements Parcelable {
/**
- * Use default behavior requesting networks. Equivalent to not setting any preference at all.
+ * Default in case this value is not set. Using it will result in an error.
*/
- public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0;
+ public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0;
/**
* If an unmetered network is available, use it.
@@ -45,17 +47,17 @@
/**
* If an unmetered network is available, use it.
* Otherwise, if a network with the OEM_PAID capability is available, use it.
- * Otherwise, the app doesn't get a network.
+ * Otherwise, the app doesn't get a default network.
*/
public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2;
/**
- * Prefer only NET_CAPABILITY_OEM_PAID networks.
+ * Use only NET_CAPABILITY_OEM_PAID networks.
*/
public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3;
/**
- * Prefer only NET_CAPABILITY_OEM_PRIVATE networks.
+ * Use only NET_CAPABILITY_OEM_PRIVATE networks.
*/
public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4;
@@ -95,8 +97,6 @@
/**
* Builder used to create {@link OemNetworkPreferences} objects. Specify the preferred Network
* to package name mappings.
- *
- * @hide
*/
public static final class Builder {
private final Bundle mNetworkMappings;
@@ -135,7 +135,7 @@
* @return The builder to facilitate chaining.
*/
@NonNull
- public Builder removeNetworkPreference(@NonNull final String packageName) {
+ public Builder clearNetworkPreference(@NonNull final String packageName) {
Objects.requireNonNull(packageName);
mNetworkMappings.remove(packageName);
return this;
@@ -160,7 +160,7 @@
/** @hide */
@IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = {
- OEM_NETWORK_PREFERENCE_DEFAULT,
+ OEM_NETWORK_PREFERENCE_UNINITIALIZED,
OEM_NETWORK_PREFERENCE_OEM_PAID,
OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK,
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY,
@@ -174,12 +174,14 @@
*
* @param value int value of OemNetworkPreference
* @return string version of OemNetworkPreference
+ *
+ * @hide
*/
@NonNull
public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) {
switch (value) {
- case OEM_NETWORK_PREFERENCE_DEFAULT:
- return "OEM_NETWORK_PREFERENCE_DEFAULT";
+ case OEM_NETWORK_PREFERENCE_UNINITIALIZED:
+ return "OEM_NETWORK_PREFERENCE_UNINITIALIZED";
case OEM_NETWORK_PREFERENCE_OEM_PAID:
return "OEM_NETWORK_PREFERENCE_OEM_PAID";
case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
similarity index 67%
rename from packages/Connectivity/framework/src/android/net/VpnManager.java
rename to core/java/android/net/VpnManager.java
index 1e30283..f472ed4 100644
--- a/packages/Connectivity/framework/src/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.content.ComponentName;
@@ -37,6 +38,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.GeneralSecurityException;
+import java.util.List;
/**
* This class provides an interface for apps to manage platform VPN profiles
@@ -76,13 +78,19 @@
@Deprecated
public static final int TYPE_VPN_LEGACY = 3;
+ /**
+ * Channel for VPN notifications.
+ * @hide
+ */
+ public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
+
/** @hide */
@IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
@Retention(RetentionPolicy.SOURCE)
public @interface VpnType {}
@NonNull private final Context mContext;
- @NonNull private final IConnectivityManager mService;
+ @NonNull private final IVpnManager mService;
private static Intent getIntentForConfirmation() {
final Intent intent = new Intent();
@@ -101,9 +109,9 @@
*
* @hide
*/
- public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
+ public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) {
mContext = checkNotNull(ctx, "missing Context");
- mService = checkNotNull(service, "missing IConnectivityManager");
+ mService = checkNotNull(service, "missing IVpnManager");
}
/**
@@ -195,6 +203,19 @@
}
/**
+ * Resets all VPN settings back to factory defaults.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void factoryReset() {
+ try {
+ mService.factoryReset();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Prepare for a VPN application.
* VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
* {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
@@ -240,6 +261,108 @@
}
/**
+ * Checks if a VPN app supports always-on mode.
+ *
+ * In order to support the always-on feature, an app has to
+ * <ul>
+ * <li>target {@link VERSION_CODES#N API 24} or above, and
+ * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
+ * </ul>
+ *
+ * @param userId The identifier of the user for whom the VPN app is installed.
+ * @param vpnPackage The canonical package name of the VPN app.
+ * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+ * @hide
+ */
+ public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+ try {
+ return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Configures an always-on VPN connection through a specific application.
+ * This connection is automatically granted and persisted after a reboot.
+ *
+ * <p>The designated package should declare a {@link VpnService} in its
+ * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+ * otherwise the call will fail.
+ *
+ * @param userId The identifier of the user to set an always-on VPN for.
+ * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+ * to remove an existing always-on VPN configuration.
+ * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+ * {@code false} otherwise.
+ * @param lockdownAllowlist The list of packages that are allowed to access network directly
+ * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+ * this method must be called when a package that should be allowed is installed or
+ * uninstalled.
+ * @return {@code true} if the package is set as always-on VPN controller;
+ * {@code false} otherwise.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
+ boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
+ try {
+ return mService.setAlwaysOnVpnPackage(
+ userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the package name of the currently set always-on VPN application.
+ * If there is no always-on VPN set, or the VPN is provided by the system instead
+ * of by an app, {@code null} will be returned.
+ *
+ * @return Package name of VPN controller responsible for always-on VPN,
+ * or {@code null} if none is set.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public String getAlwaysOnVpnPackageForUser(int userId) {
+ try {
+ return mService.getAlwaysOnVpnPackage(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return whether always-on VPN is in lockdown mode.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean isVpnLockdownEnabled(int userId) {
+ try {
+ return mService.isVpnLockdownEnabled(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return the list of packages that are allowed to access network when always-on VPN is in
+ * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public List<String> getVpnLockdownAllowlist(int userId) {
+ try {
+ return mService.getVpnLockdownAllowlist(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the legacy VPN information for the specified user ID.
* @hide
*/
diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java
similarity index 98%
rename from packages/Connectivity/framework/src/android/net/VpnService.java
rename to core/java/android/net/VpnService.java
index 8e90a119..e43b0b6 100644
--- a/packages/Connectivity/framework/src/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -170,12 +170,11 @@
"android.net.VpnService.SUPPORTS_ALWAYS_ON";
/**
- * Use IConnectivityManager since those methods are hidden and not
- * available in ConnectivityManager.
+ * Use IVpnManager since those methods are hidden and not available in VpnManager.
*/
- private static IConnectivityManager getService() {
- return IConnectivityManager.Stub.asInterface(
- ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ private static IVpnManager getService() {
+ return IVpnManager.Stub.asInterface(
+ ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE));
}
/**
@@ -226,15 +225,15 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.CONTROL_VPN)
public static void prepareAndAuthorize(Context context) {
- IConnectivityManager cm = getService();
+ IVpnManager vm = getService();
String packageName = context.getPackageName();
try {
// Only prepare if we're not already prepared.
int userId = context.getUserId();
- if (!cm.prepareVpn(packageName, null, userId)) {
- cm.prepareVpn(null, packageName, userId);
+ if (!vm.prepareVpn(packageName, null, userId)) {
+ vm.prepareVpn(null, packageName, userId);
}
- cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
+ vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
} catch (RemoteException e) {
// ignore
}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 72a6e16..bf229e0 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -47,7 +47,9 @@
POWER_COMPONENT_SYSTEM_SERVICES,
POWER_COMPONENT_SENSORS,
POWER_COMPONENT_GNSS,
+ POWER_COMPONENT_WAKELOCK,
POWER_COMPONENT_SCREEN,
+ POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface PowerComponent {
@@ -64,9 +66,14 @@
public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
public static final int POWER_COMPONENT_SENSORS = 9;
public static final int POWER_COMPONENT_GNSS = 10;
+ public static final int POWER_COMPONENT_WAKELOCK = 12;
public static final int POWER_COMPONENT_SCREEN = 13;
+ // Power that is re-attributed to other battery consumers. For example, for System Server
+ // this represents the power attributed to apps requesting system services.
+ // The value should be negative or zero.
+ public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 14;
- public static final int POWER_COMPONENT_COUNT = 14;
+ public static final int POWER_COMPONENT_COUNT = 15;
public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -87,6 +94,7 @@
TIME_COMPONENT_MOBILE_RADIO,
TIME_COMPONENT_SENSORS,
TIME_COMPONENT_GNSS,
+ TIME_COMPONENT_WAKELOCK,
TIME_COMPONENT_SCREEN,
})
@Retention(RetentionPolicy.SOURCE)
@@ -104,6 +112,7 @@
public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
public static final int TIME_COMPONENT_SENSORS = 9;
public static final int TIME_COMPONENT_GNSS = 10;
+ public static final int TIME_COMPONENT_WAKELOCK = 12;
public static final int TIME_COMPONENT_SCREEN = 13;
public static final int TIME_COMPONENT_COUNT = 14;
@@ -236,5 +245,13 @@
componentUsageTimeMillis);
return (T) this;
}
+
+ /**
+ * Returns the total power accumulated by this builder so far. It may change
+ * by the time the {@code build()} method is called.
+ */
+ public double getTotalPower() {
+ return mPowerComponentsBuilder.getTotalPower();
+ }
}
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc86a60..01a8901 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -994,6 +994,19 @@
*/
public abstract long getScreenOnEnergy();
+ /**
+ * Returns the energies used by this uid for each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+ * type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+ * supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
public static abstract class Sensor {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2511,6 +2524,19 @@
*/
public abstract long getScreenDozeEnergy();
+ /**
+ * Returns the energies used for each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+ * type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+ * supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
@@ -5268,6 +5294,15 @@
pw.print(" flash=");
printmAh(pw, bs.flashlightPowerMah);
}
+ if (bs.customMeasuredPowerMah != null) {
+ for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) {
+ final double customPowerMah = bs.customMeasuredPowerMah[idx];
+ if (customPowerMah != 0) {
+ pw.print(" custom[" + idx + "]=");
+ printmAh(pw, customPowerMah);
+ }
+ }
+ }
pw.print(" )");
}
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index cb4e9cb..c8e682c 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.util.SparseArray;
import com.android.internal.util.Preconditions;
@@ -86,7 +87,20 @@
return 0;
}
- /** @hide */
+ /**
+ * Gets the estimated duration of the combined vibration in milliseconds.
+ *
+ * <p>For synced combinations this means the maximum duration of any individual {@link
+ * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+ *
+ * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
+ * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
+ * Prebaked effects where the length is device and potentially run-time dependent), this returns
+ * -1.
+ *
+ * @hide
+ */
+ @TestApi
public abstract long getDuration();
/** @hide */
@@ -256,6 +270,7 @@
*
* @hide
*/
+ @TestApi
public static final class Mono extends CombinedVibrationEffect {
private final VibrationEffect mEffect;
@@ -267,6 +282,7 @@
mEffect = effect;
}
+ @NonNull
public VibrationEffect getEffect() {
return mEffect;
}
@@ -282,6 +298,7 @@
mEffect.validate();
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
return true;
@@ -307,7 +324,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_MONO);
mEffect.writeToParcel(out, flags);
}
@@ -335,6 +357,7 @@
*
* @hide
*/
+ @TestApi
public static final class Stereo extends CombinedVibrationEffect {
/** Mapping vibrator ids to effects. */
@@ -357,6 +380,7 @@
}
/** Effects to be performed in sync, where each key represents the vibrator id. */
+ @NonNull
public SparseArray<VibrationEffect> getEffects() {
return mEffects;
}
@@ -394,6 +418,7 @@
}
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
return mEffects.indexOfKey(vibratorId) >= 0;
@@ -418,7 +443,7 @@
@Override
public int hashCode() {
- return Objects.hash(mEffects);
+ return mEffects.contentHashCode();
}
@Override
@@ -427,7 +452,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_STEREO);
out.writeInt(mEffects.size());
for (int i = 0; i < mEffects.size(); i++) {
@@ -459,6 +489,7 @@
*
* @hide
*/
+ @TestApi
public static final class Sequential extends CombinedVibrationEffect {
private final List<CombinedVibrationEffect> mEffects;
private final List<Integer> mDelays;
@@ -480,11 +511,13 @@
}
/** Effects to be performed in sequence. */
+ @NonNull
public List<CombinedVibrationEffect> getEffects() {
return mEffects;
}
/** Delay to be applied before each effect in {@link #getEffects()}. */
+ @NonNull
public List<Integer> getDelays() {
return mDelays;
}
@@ -542,6 +575,7 @@
}
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
final int effectCount = mEffects.size();
@@ -564,7 +598,7 @@
@Override
public int hashCode() {
- return Objects.hash(mEffects);
+ return Objects.hash(mEffects, mDelays);
}
@Override
@@ -573,7 +607,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
out.writeInt(mEffects.size());
for (int i = 0; i < mEffects.size(); i++) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 6a76da2..e3b13f4 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1902,7 +1902,8 @@
* Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long
* array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
* Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
- * another array to also retrieve the separate memtrack size.
+ * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the
+ * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other.
*
* @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
* @hide
@@ -2565,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.
*
@@ -2589,6 +2598,13 @@
public static native long getIonPoolsSizeKb();
/**
+ * Return GPU DMA buffer usage in kB or -1 on error.
+ *
+ * @hide
+ */
+ public static native long getGpuDmaBufUsageKb();
+
+ /**
* Return DMA-BUF memory mapped by processes in kB.
* Notes:
* * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
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/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
deleted file mode 100644
index 1cd48dc..0000000
--- a/core/java/android/os/IVibratorService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.os.VibrationEffect;
-import android.os.VibrationAttributes;
-import android.os.VibratorInfo;
-import android.os.IVibratorStateListener;
-
-/** {@hide} */
-interface IVibratorService
-{
- boolean hasVibrator();
- boolean isVibrating();
- VibratorInfo getVibratorInfo();
- boolean registerVibratorStateListener(in IVibratorStateListener listener);
- boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
- boolean hasAmplitudeControl();
- void vibrate(int uid, String opPkg, in VibrationEffect effect,
- in VibrationAttributes attributes, String reason, IBinder token);
- void cancelVibrate(IBinder token);
-}
-
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 2559a33..a04047d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,15 +1,18 @@
# Haptics
+per-file CombinedVibrationEffect.aidl = michaelwr@google.com
+per-file CombinedVibrationEffect.java = michaelwr@google.com
per-file ExternalVibration.aidl = michaelwr@google.com
per-file ExternalVibration.java = michaelwr@google.com
per-file IExternalVibrationController.aidl = michaelwr@google.com
per-file IExternalVibratorService.aidl = michaelwr@google.com
per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file IVibratorService.aidl = michaelwr@google.com
per-file NullVibrator.java = michaelwr@google.com
per-file SystemVibrator.java = michaelwr@google.com
+per-file SystemVibratorManager.java = michaelwr@google.com
per-file VibrationEffect.aidl = michaelwr@google.com
per-file VibrationEffect.java = michaelwr@google.com
per-file Vibrator.java = michaelwr@google.com
+per-file VibratorManager.java = michaelwr@google.com
# PowerManager
per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 1337d55..ac23285 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -38,11 +38,7 @@
mCustomPowerComponentCount = builder.mCustomPowerComponentCount;
mPowerComponents = builder.mPowerComponents;
mTimeComponents = builder.mTimeComponents;
- double totalPower = 0;
- for (int i = mPowerComponents.length - 1; i >= 0; i--) {
- totalPower += mPowerComponents[i];
- }
- mTotalPowerConsumed = totalPower;
+ mTotalPowerConsumed = builder.getTotalPower();
}
PowerComponents(@NonNull Parcel source) {
@@ -264,6 +260,18 @@
}
/**
+ * Returns the total power accumulated by this builder so far. It may change
+ * by the time the {@code build()} method is called.
+ */
+ public double getTotalPower() {
+ double totalPower = 0;
+ for (int i = mPowerComponents.length - 1; i >= 0; i--) {
+ totalPower += mPowerComponents[i];
+ }
+ return totalPower;
+ }
+
+ /**
* Creates a read-only object out of the Builder values.
*/
@NonNull
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/Process.java b/core/java/android/os/Process.java
index 8068c87..54d2df8 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -970,6 +970,16 @@
throws IllegalArgumentException, SecurityException;
/**
+ *
+ * Create a new process group in the cgroup uid/pid hierarchy
+ *
+ * @return <0 in case of error
+ *
+ * @hide
+ */
+ public static final native int createProcessGroup(int uid, int pid);
+
+ /**
* On some devices, the foreground process may have one or more CPU
* cores exclusively reserved for it. This method can be used to
* retrieve which cores that are (if any), so the calling process
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/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 30afe38..b42a495 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,19 +17,18 @@
package android.os;
import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -41,142 +40,46 @@
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
- private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
- private static final int VIBRATOR_PRESENT_YES = 1;
- private static final int VIBRATOR_PRESENT_NO = 2;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
- private @interface VibratorPresent {}
-
- private final IVibratorService mService;
- private final IVibratorManagerService mManagerService;
- private final Object mLock = new Object();
- private final Binder mToken = new Binder();
+ private final VibratorManager mVibratorManager;
private final Context mContext;
- @GuardedBy("mLock")
- private VibratorInfo mVibratorInfo;
- @GuardedBy("mLock")
- @VibratorPresent
- private int mVibratorPresent;
- @GuardedBy("mDelegates")
- private final ArrayMap<OnVibratorStateChangedListener,
- OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
+ @GuardedBy("mBrokenListeners")
+ private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
- @UnsupportedAppUsage
- public SystemVibrator() {
- mContext = null;
- mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
- mManagerService = IVibratorManagerService.Stub.asInterface(
- ServiceManager.getService("vibrator_manager"));
- }
+ @GuardedBy("mRegisteredListeners")
+ private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+ mRegisteredListeners = new ArrayMap<>();
@UnsupportedAppUsage
public SystemVibrator(Context context) {
super(context);
mContext = context;
- mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
- mManagerService = IVibratorManagerService.Stub.asInterface(
- ServiceManager.getService("vibrator_manager"));
+ mVibratorManager = mContext.getSystemService(VibratorManager.class);
}
@Override
public boolean hasVibrator() {
- try {
- synchronized (mLock) {
- if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
- mVibratorPresent =
- mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
- }
- return mVibratorPresent == VIBRATOR_PRESENT_YES;
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to query vibrator presence", e);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
return false;
}
+ return mVibratorManager.getVibratorIds().length > 0;
}
- /**
- * Check whether the vibrator is vibrating.
- *
- * @return True if the hardware is vibrating, otherwise false.
- */
@Override
public boolean isVibrating() {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
return false;
}
- try {
- return mService.isVibrating();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ for (int vibratorId : mVibratorManager.getVibratorIds()) {
+ if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+ return true;
+ }
}
return false;
}
- private class OnVibratorStateChangedListenerDelegate extends
- IVibratorStateListener.Stub {
- private final Executor mExecutor;
- private final OnVibratorStateChangedListener mListener;
-
- OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
- @NonNull Executor executor) {
- mExecutor = executor;
- mListener = listener;
- }
-
- @Override
- public void onVibrating(boolean isVibrating) {
- mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
- }
- }
-
- /**
- * Adds a listener for vibrator state change. If the listener was previously added and not
- * removed, this call will be ignored.
- *
- * @param listener Listener to be added.
- * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
- */
- @Override
- public void addVibratorStateListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnVibratorStateChangedListener listener) {
- Objects.requireNonNull(listener);
- Objects.requireNonNull(executor);
- if (mService == null) {
- Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
- return;
- }
-
- synchronized (mDelegates) {
- // If listener is already registered, reject and return.
- if (mDelegates.containsKey(listener)) {
- Log.w(TAG, "Listener already registered.");
- return;
- }
- try {
- final OnVibratorStateChangedListenerDelegate delegate =
- new OnVibratorStateChangedListenerDelegate(listener, executor);
- if (!mService.registerVibratorStateListener(delegate)) {
- Log.w(TAG, "Failed to register vibrate state listener");
- return;
- }
- mDelegates.put(listener, delegate);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
- * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
- * If the listener was previously added and not removed, this call will be ignored.
- *
- * @param listener listener to be added
- */
@Override
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
@@ -187,88 +90,128 @@
addVibratorStateListener(mContext.getMainExecutor(), listener);
}
- /**
- * Removes the listener for vibrator state changes. If the listener was not previously
- * registered, this call will do nothing.
- *
- * @param listener Listener to be removed.
- */
@Override
- public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
- if (mService == null) {
- Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+ Objects.requireNonNull(executor);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
return;
}
- synchronized (mDelegates) {
- // Check if the listener is registered, otherwise will return.
- if (mDelegates.containsKey(listener)) {
- final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
- try {
- if (!mService.unregisterVibratorStateListener(delegate)) {
- Log.w(TAG, "Failed to unregister vibrate state listener");
- return;
- }
- mDelegates.remove(listener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ AllVibratorsStateListener delegate = null;
+ try {
+ synchronized (mRegisteredListeners) {
+ // If listener is already registered, reject and return.
+ if (mRegisteredListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ delegate = new AllVibratorsStateListener(executor, listener);
+ delegate.register(mVibratorManager);
+ mRegisteredListeners.put(listener, delegate);
+ delegate = null;
+ }
+ } finally {
+ if (delegate != null && delegate.hasRegisteredListeners()) {
+ // The delegate listener was left in a partial state with listeners registered to
+ // some but not all vibrators. Keep track of this to try to unregister them later.
+ synchronized (mBrokenListeners) {
+ mBrokenListeners.add(delegate);
}
}
+ tryUnregisterBrokenListeners();
}
}
@Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+ return;
+ }
+ synchronized (mRegisteredListeners) {
+ if (mRegisteredListeners.containsKey(listener)) {
+ AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+ delegate.unregister(mVibratorManager);
+ mRegisteredListeners.remove(listener);
+ }
+ }
+ tryUnregisterBrokenListeners();
+ }
+
+ @Override
public boolean hasAmplitudeControl() {
- if (mService == null) {
- Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
return false;
}
- try {
- return mService.hasAmplitudeControl();
- } catch (RemoteException e) {
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ return false;
}
- return false;
+ for (int vibratorId : vibratorIds) {
+ if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
+ return false;
+ }
+ }
+ return true;
}
@Override
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
AudioAttributes attributes) {
- if (mManagerService == null) {
- Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
return false;
}
- try {
- VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
- CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
- return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set always-on effect.", e);
- }
- return false;
+ VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
}
@Override
public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
String reason, @NonNull VibrationAttributes attributes) {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
return;
}
- try {
- mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
}
@Override
public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
- VibratorInfo vibratorInfo = getVibratorInfo();
int[] supported = new int[effectIds.length];
- for (int i = 0; i < effectIds.length; i++) {
- supported[i] = vibratorInfo == null
- ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
- : vibratorInfo.isEffectSupported(effectIds[i]);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+ return supported;
+ }
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+ return supported;
+ }
+ int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
+ for (int i = 0; i < vibratorIds.length; i++) {
+ vibratorSupportMap[i] = mVibratorManager.getVibrator(
+ vibratorIds[i]).areEffectsSupported(effectIds);
+ }
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
+ for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
+ for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+ int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
+ if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
+ supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+ break;
+ } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
+ supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ }
+ }
}
return supported;
}
@@ -276,42 +219,169 @@
@Override
public boolean[] arePrimitivesSupported(
@NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
- VibratorInfo vibratorInfo = getVibratorInfo();
boolean[] supported = new boolean[primitiveIds.length];
- for (int i = 0; i < primitiveIds.length; i++) {
- supported[i] = vibratorInfo == null
- ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
+ Arrays.fill(supported, false);
+ return supported;
+ }
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ Arrays.fill(supported, false);
+ return supported;
+ }
+ boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
+ for (int i = 0; i < vibratorIds.length; i++) {
+ vibratorSupportMap[i] = mVibratorManager.getVibrator(
+ vibratorIds[i]).arePrimitivesSupported(primitiveIds);
+ }
+ Arrays.fill(supported, true);
+ for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
+ for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+ if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
+ supported[primitiveIdx] = false;
+ break;
+ }
+ }
}
return supported;
}
@Override
public void cancel() {
- if (mService == null) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
return;
}
- try {
- mService.cancelVibrate(mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to cancel vibration.", e);
+ mVibratorManager.cancel();
+ }
+
+ /**
+ * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+ * that were left registered to vibrators after failures to register them to all vibrators.
+ *
+ * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+ * and also fails to unregister any previously registered single listeners to other vibrators.
+ *
+ * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+ * fail silently and attempt to unregister the same broken listener later.
+ */
+ private void tryUnregisterBrokenListeners() {
+ synchronized (mBrokenListeners) {
+ try {
+ for (int i = mBrokenListeners.size(); --i >= 0; ) {
+ mBrokenListeners.get(i).unregister(mVibratorManager);
+ mBrokenListeners.remove(i);
+ }
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Failed to unregister broken listener", e);
+ }
}
}
- @Nullable
- private VibratorInfo getVibratorInfo() {
- try {
+ /** Listener for a single vibrator state change. */
+ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+ private final AllVibratorsStateListener mAllVibratorsListener;
+ private final int mVibratorIdx;
+
+ SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+ mAllVibratorsListener = listener;
+ mVibratorIdx = vibratorIdx;
+ }
+
+ @Override
+ public void onVibratorStateChanged(boolean isVibrating) {
+ mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+ }
+ }
+
+ /** Listener for all vibrators state change. */
+ private static class AllVibratorsStateListener {
+ private final Object mLock = new Object();
+ private final Executor mExecutor;
+ private final OnVibratorStateChangedListener mDelegate;
+
+ @GuardedBy("mLock")
+ private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+ new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private int mInitializedMask;
+ @GuardedBy("mLock")
+ private int mVibratingMask;
+
+ AllVibratorsStateListener(@NonNull Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ mExecutor = executor;
+ mDelegate = listener;
+ }
+
+ boolean hasRegisteredListeners() {
synchronized (mLock) {
- if (mVibratorInfo != null) {
- return mVibratorInfo;
- }
- if (mService == null) {
- return null;
- }
- return mVibratorInfo = mService.getVibratorInfo();
+ return mVibratorListeners.size() > 0;
}
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to query vibrator info");
- throw e.rethrowFromSystemServer();
+ }
+
+ void register(VibratorManager vibratorManager) {
+ int[] vibratorIds = vibratorManager.getVibratorIds();
+ synchronized (mLock) {
+ for (int i = 0; i < vibratorIds.length; i++) {
+ int vibratorId = vibratorIds[i];
+ SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+ try {
+ vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+ listener);
+ mVibratorListeners.put(vibratorId, listener);
+ } catch (RuntimeException e) {
+ try {
+ unregister(vibratorManager);
+ } catch (RuntimeException e1) {
+ Log.w(TAG,
+ "Failed to unregister listener while recovering from a failed "
+ + "register call", e1);
+ }
+ throw e;
+ }
+ }
+ }
+ }
+
+ void unregister(VibratorManager vibratorManager) {
+ synchronized (mLock) {
+ for (int i = mVibratorListeners.size(); --i >= 0; ) {
+ int vibratorId = mVibratorListeners.keyAt(i);
+ SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+ vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+ mVibratorListeners.removeAt(i);
+ }
+ }
+ }
+
+ void onVibrating(int vibratorIdx, boolean vibrating) {
+ mExecutor.execute(() -> {
+ boolean anyVibrating;
+ synchronized (mLock) {
+ int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+ int vibratorMask = 1 << vibratorIdx;
+ if ((mInitializedMask & vibratorMask) == 0) {
+ // First state report for this vibrator, set vibrating initial value.
+ mInitializedMask |= vibratorMask;
+ mVibratingMask |= vibrating ? vibratorMask : 0;
+ } else {
+ // Flip vibrating value, if changed.
+ boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
+ if (prevVibrating != vibrating) {
+ mVibratingMask ^= vibratorMask;
+ }
+ }
+ if (mInitializedMask != allInitializedMask) {
+ // Wait for all vibrators initial state to be reported before delegating.
+ return;
+ }
+ anyVibrating = mVibratingMask != 0;
+ }
+ mDelegate.onVibratorStateChanged(anyVibrating);
+ });
}
}
}
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..b528eb1
--- /dev/null
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -0,0 +1,360 @@
+/*
+ * 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.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+ private static final String TAG = "VibratorManager";
+
+ private final IVibratorManagerService mService;
+ private final Context mContext;
+ private final Binder mToken = new Binder();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private int[] mVibratorIds;
+ @GuardedBy("mLock")
+ private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+ OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public SystemVibratorManager(Context context) {
+ super(context);
+ mContext = context;
+ mService = IVibratorManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+ }
+
+ @NonNull
+ @Override
+ public int[] getVibratorIds() {
+ synchronized (mLock) {
+ if (mVibratorIds != null) {
+ return mVibratorIds;
+ }
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+ } else {
+ return mVibratorIds = mService.getVibratorIds();
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return new int[0];
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getVibrator(int vibratorId) {
+ synchronized (mLock) {
+ Vibrator vibrator = mVibrators.get(vibratorId);
+ if (vibrator != null) {
+ return vibrator;
+ }
+ VibratorInfo info = null;
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+ } else {
+ info = mService.getVibratorInfo(vibratorId);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ if (info != null) {
+ vibrator = new SingleVibrator(info);
+ mVibrators.put(vibratorId, vibrator);
+ } else {
+ vibrator = NullVibrator.getInstance();
+ }
+ return vibrator;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getDefaultVibrator() {
+ return mContext.getSystemService(Vibrator.class);
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+ return false;
+ }
+ try {
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration.", e);
+ }
+ }
+
+ /** Listener for vibrations on a single vibrator. */
+ private static class OnVibratorStateChangedListenerDelegate extends
+ IVibratorStateListener.Stub {
+ private final Executor mExecutor;
+ private final Vibrator.OnVibratorStateChangedListener mListener;
+
+ OnVibratorStateChangedListenerDelegate(
+ @NonNull Vibrator.OnVibratorStateChangedListener listener,
+ @NonNull Executor executor) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onVibrating(boolean isVibrating) {
+ mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+ }
+ }
+
+ /** Controls vibrations on a single vibrator. */
+ private final class SingleVibrator extends Vibrator {
+ private final VibratorInfo mVibratorInfo;
+
+ SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+ mVibratorInfo = vibratorInfo;
+ }
+
+ @Override
+ public int getId() {
+ return mVibratorInfo.getId();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return mVibratorInfo.hasAmplitudeControl();
+ }
+
+ @NonNull
+ @Override
+ public int[] areEffectsSupported(@NonNull int... effectIds) {
+ int[] supported = new int[effectIds.length];
+ for (int i = 0; i < effectIds.length; i++) {
+ supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
+ }
+ return supported;
+ }
+
+ @Override
+ public boolean[] arePrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ boolean[] supported = new boolean[primitiveIds.length];
+ for (int i = 0; i < primitiveIds.length; i++) {
+ supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+ }
+ return supported;
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return false;
+ }
+ try {
+ VibrationAttributes attr = new VibrationAttributes.Builder(
+ attributes, effect).build();
+ CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+ .addVibrator(mVibratorInfo.getId(), effect)
+ .combine();
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+ @NonNull VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return;
+ }
+ try {
+ CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+ .addVibrator(mVibratorInfo.getId(), vibe)
+ .combine();
+ mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
+ }
+ }
+
+ @Override
+ public boolean isVibrating() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.isVibrating(mVibratorInfo.getId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ @Override
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mContext == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+ return;
+ }
+ addVibratorStateListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ Objects.requireNonNull(executor);
+ if (mService == null) {
+ Log.w(TAG,
+ "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // If listener is already registered, reject and return.
+ if (mListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ try {
+ OnVibratorStateChangedListenerDelegate delegate =
+ new OnVibratorStateChangedListenerDelegate(listener, executor);
+ if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+ Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.put(listener, delegate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mService == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId() + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // Check if the listener is registered, otherwise will return.
+ if (mListeners.containsKey(listener)) {
+ OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+ try {
+ if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+ delegate)) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.remove(listener);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7d85d13..d6fa733 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,15 @@
}
/**
+ * Return the ID of this vibrator.
+ *
+ * @return The id of the vibrator controlled by this service.
+ */
+ public int getId() {
+ return -1;
+ }
+
+ /**
* Check whether the hardware has a vibrator.
*
* @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 1d5a587..5dd38b6 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -17,45 +17,123 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
/**
- * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
- * a synchronized fashion.
+ * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
*/
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
public abstract class VibratorManager {
- /** @hide */
- protected static final String TAG = "VibratorManager";
+ private static final String TAG = "VibratorManager";
+
+ private final String mPackageName;
/**
- * {@hide}
+ * @hide to prevent subclassing from outside of the framework
*/
public VibratorManager() {
+ mPackageName = ActivityThread.currentPackageName();
}
/**
- * This method lists all available actuator ids, returning a possible empty list.
- * If the device has only a single actuator, this should return a single entry with a
- * default id.
+ * @hide to prevent subclassing from outside of the framework
+ */
+ protected VibratorManager(Context context) {
+ mPackageName = context.getOpPackageName();
+ }
+
+ /**
+ * List all available vibrator ids, returning a possible empty list.
+ *
+ * @return An array containing the ids of the vibrators available on the device.
*/
@NonNull
public abstract int[] getVibratorIds();
/**
- * Returns a Vibrator service for given id.
- * This allows users to perform a vibration effect on a single actuator.
- */
+ * Retrieve a single vibrator by id.
+ *
+ * @param vibratorId The id of the vibrator to be retrieved.
+ * @return The vibrator with given {@code vibratorId}, never null.
+ */
@NonNull
public abstract Vibrator getVibrator(int vibratorId);
/**
- * Returns the system default Vibrator service.
- */
+ * Returns the system default Vibrator service.
+ */
@NonNull
public abstract Vibrator getDefaultVibrator();
/**
- * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
- * to the respective actuator, in sync.
+ * Configure an always-on haptics effect.
+ *
+ * @hide
*/
- public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+ * VibrationEffect} to be played on one or more vibrators.
+ * </p>
+ *
+ * @param effect an array of longs of times for which to turn the vibrator on or off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibrationEffect effect) {
+ vibrate(effect, null);
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+ * VibrationEffect} to be played on one or more vibrators.
+ * </p>
+ *
+ * @param effect an array of longs of times for which to turn the vibrator on or off.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibrationEffect effect,
+ @Nullable VibrationAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+ }
+
+ /**
+ * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
+ * caller to specify the vibration is owned by someone else and set reason for vibration.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes);
+
+ /**
+ * Turn all the vibrators off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel();
}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 0ff68fc..0589994 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -241,6 +241,13 @@
}
/**
+ * Checks if device supports V2 calls (e.g. PerUid).
+ */
+ public static boolean isV2Available() {
+ return nativeIsV2Available();
+ }
+
+ /**
* Checks if Incremental installations are allowed.
* A developer can disable Incremental installations by setting the property.
*/
@@ -439,6 +446,7 @@
/* Native methods */
private static native boolean nativeIsEnabled();
+ private static native boolean nativeIsV2Available();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 137825
-moltmann@google.com
evanseverson@google.com
ntmyren@google.com
zhanghai@google.com
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 85e9fdb..0e35ef9 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -693,13 +693,14 @@
for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
OpUsage usage = rawUsages.get(usageNum);
+ // If this attribution is a proxy, remove it
+ if (toRemoveProxies.contains(usage.toPackageAttr())) {
+ continue;
+ }
+
// If this attribution has a special attribution, do not remove it
if (specialAttributions.contains(usage.toPackageAttr())) {
deDuped.add(usage);
- }
-
- // If this attribution is a proxy, remove it
- if (toRemoveProxies.contains(usage.toPackageAttr())) {
continue;
}
diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permissionpresenterservice/OWNERS
+++ b/core/java/android/permissionpresenterservice/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 137825
-moltmann@google.com
evanseverson@google.com
ntmyren@google.com
zhanghai@google.com
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/pdf/OWNERS
+++ b/core/java/android/print/pdf/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/recommendation/OWNERS
+++ b/core/java/android/printservice/recommendation/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef81ed7..f02e532 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10780,6 +10780,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/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java
index 6fdfaab..9de75ca 100644
--- a/core/java/android/service/notification/NotificationListenerFilter.java
+++ b/core/java/android/service/notification/NotificationListenerFilter.java
@@ -20,6 +20,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -31,7 +32,8 @@
*/
public class NotificationListenerFilter implements Parcelable {
private int mAllowedNotificationTypes;
- private ArraySet<String> mDisallowedPackages;
+ // VersionedPackage is holding the pkg name and pkg uid
+ private ArraySet<VersionedPackage> mDisallowedPackages;
public NotificationListenerFilter() {
mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS
@@ -41,7 +43,7 @@
mDisallowedPackages = new ArraySet<>();
}
- public NotificationListenerFilter(int types, ArraySet<String> pkgs) {
+ public NotificationListenerFilter(int types, ArraySet<VersionedPackage> pkgs) {
mAllowedNotificationTypes = types;
mDisallowedPackages = pkgs;
}
@@ -51,7 +53,8 @@
*/
protected NotificationListenerFilter(Parcel in) {
mAllowedNotificationTypes = in.readInt();
- mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader());
+ mDisallowedPackages = (ArraySet<VersionedPackage>) in.readArraySet(
+ VersionedPackage.class.getClassLoader());
}
@Override
@@ -77,7 +80,7 @@
return (mAllowedNotificationTypes & type) != 0;
}
- public boolean isPackageAllowed(String pkg) {
+ public boolean isPackageAllowed(VersionedPackage pkg) {
return !mDisallowedPackages.contains(pkg);
}
@@ -85,7 +88,7 @@
return mAllowedNotificationTypes;
}
- public ArraySet<String> getDisallowedPackages() {
+ public ArraySet<VersionedPackage> getDisallowedPackages() {
return mDisallowedPackages;
}
@@ -93,10 +96,18 @@
mAllowedNotificationTypes = types;
}
- public void setDisallowedPackages(ArraySet<String> pkgs) {
+ public void setDisallowedPackages(ArraySet<VersionedPackage> pkgs) {
mDisallowedPackages = pkgs;
}
+ public void removePackage(VersionedPackage pkg) {
+ mDisallowedPackages.remove(pkg);
+ }
+
+ public void addPackage(VersionedPackage pkg) {
+ mDisallowedPackages.add(pkg);
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 64cddc3..f66f85b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -80,6 +80,10 @@
* <intent-filter>
* <action android:name="android.service.notification.NotificationListenerService" />
* </intent-filter>
+ * <meta-data
+ * android:name="android.service.notification.default_filter_types"
+ * android:value="1,2">
+ * </meta-data>
* </service></pre>
*
* <p>The service should wait for the {@link #onListenerConnected()} event
@@ -103,6 +107,21 @@
private final String TAG = getClass().getSimpleName();
/**
+ * The name of the {@code meta-data} tag containing a comma separated list of default
+ * integer notification types that should be provided to this listener. See
+ * {@link #FLAG_FILTER_TYPE_ONGOING},
+ * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
+ * and {@link #FLAG_FILTER_TYPE_SILENT}.
+ * <p>This value will only be read if the app has not previously specified a default type list,
+ * and if the user has not overridden the allowed types.</p>
+ * <p>An absent value means 'allow all types'.
+ * A present but empty value means 'allow no types'.</p>
+ *
+ */
+ public static final String META_DATA_DEFAULT_FILTER_TYPES
+ = "android.service.notification.default_filter_types";
+
+ /**
* {@link #getCurrentInterruptionFilter() Interruption filter} constant -
* Normal interruption filter.
*/
@@ -254,23 +273,19 @@
/**
* A flag value indicating that this notification listener can see conversation type
* notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
/**
* A flag value indicating that this notification listener can see altering type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_ALERTING = 2;
/**
* A flag value indicating that this notification listener can see silent type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_SILENT = 4;
/**
* A flag value indicating that this notification listener can see important
* ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_ONGOING = 8;
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index a750b68..87add57 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -95,6 +95,21 @@
public static final String EXTRA_ERROR =
"android.service.storage.extra.error";
+ /**
+ * {@link Bundle} key for a package name {@link String} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name";
+
+ /**
+ * {@link Bundle} key for a {@link Long} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_ANR_TIMEOUT_MS =
+ "android.service.storage.extra.anr_timeout_ms";
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_SESSION_"},
value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
@@ -162,6 +177,15 @@
throw new UnsupportedOperationException("onFreeCacheRequested not implemented");
}
+ /**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long onGetAnrDelayMillis(@NonNull String packageName, int uid) {
+ throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented");
+ }
+
@Override
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
@@ -222,6 +246,19 @@
});
}
+ @Override
+ public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback)
+ throws RemoteException {
+ mHandler.post(() -> {
+ try {
+ long timeoutMs = onGetAnrDelayMillis(packageName, uid);
+ sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback);
+ } catch (Throwable t) {
+ sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback);
+ }
+ });
+ }
+
private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_SESSION_ID, sessionId);
@@ -230,5 +267,16 @@
}
callback.sendResult(bundle);
}
+
+ private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable,
+ RemoteCallback callback) {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+ bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs);
+ if (throwable != null) {
+ bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+ }
+ callback.sendResult(bundle);
+ }
}
}
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index d06671b..2e0bd86 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,4 +32,5 @@
in RemoteCallback callback);
void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
in RemoteCallback callback);
+ void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback);
}
\ No newline at end of file
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/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index cbc0479..20f0598 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -32,7 +32,6 @@
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
- private static native void nativeKill(long ptr);
/** Create a new connection with the surface flinger. */
@UnsupportedAppUsage
@@ -44,22 +43,22 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mNativeClient != 0) {
- nativeDestroy(mNativeClient);
- }
+ kill();
} finally {
super.finalize();
}
}
/**
- * Forcibly detach native resources associated with this object.
- * Unlike destroy(), after this call any surfaces that were created
- * from the session will no longer work.
+ * Remove the reference to the native Session object. The native object may still exist if
+ * there are other references to it, but it cannot be accessed from this Java object anymore.
*/
@UnsupportedAppUsage
public void kill() {
- nativeKill(mNativeClient);
+ if (mNativeClient != 0) {
+ nativeDestroy(mNativeClient);
+ mNativeClient = 0;
+ }
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70ec2d4..6eba83f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1083,7 +1083,12 @@
if (creating) {
updateOpaqueFlag();
- mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ if (mUseBlastAdapter) {
+ createBlastSurfaceControls(viewRoot, name);
+ } else {
+ mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
+ }
} else if (mSurfaceControl == null) {
return;
}
@@ -1220,53 +1225,77 @@
* out, the old surface can be persevered until the new one has drawn by keeping the reference
* of the old SurfaceControl alive.
*/
- private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-
- SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+ private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) {
+ final SurfaceControl previousSurfaceControl = mSurfaceControl;
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
.setLocalOwnerView(this)
.setParent(viewRoot.getBoundsLayer())
- .setCallsite("SurfaceView.updateSurface");
+ .setCallsite("SurfaceView.updateSurface")
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFlags(mSurfaceFlags)
+ .setFormat(mFormat)
+ .build();
+ mBackgroundControl = createBackgroundControl(name);
+ return previousSurfaceControl;
+ }
- final SurfaceControl previousSurfaceControl;
- if (mUseBlastAdapter) {
- mSurfaceControl = builder
+ private SurfaceControl createBackgroundControl(String name) {
+ return new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for " + name)
+ .setLocalOwnerView(this)
+ .setOpaque(true)
+ .setColorLayer()
+ .setParent(mSurfaceControl)
+ .setCallsite("SurfaceView.updateSurface")
+ .build();
+ }
+
+ // We don't recreate the surface controls but only recreate the adapter. Since the blast layer
+ // is still alive, the old buffers will continue to be presented until replaced by buffers from
+ // the new adapter. This means we do not need to track the old surface control and destroy it
+ // after the client has drawn to avoid any flickers.
+ private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+ if (mSurfaceControl == null) {
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setLocalOwnerView(this)
+ .setParent(viewRoot.getBoundsLayer())
+ .setCallsite("SurfaceView.updateSurface")
.setContainerLayer()
.build();
- previousSurfaceControl = mBlastSurfaceControl;
+ }
+
+ if (mBlastSurfaceControl == null) {
mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name + "(BLAST)")
.setLocalOwnerView(this)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
.setParent(mSurfaceControl)
.setFlags(mSurfaceFlags)
.setHidden(false)
.setBLASTLayer()
.setCallsite("SurfaceView.updateSurface")
.build();
- mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat, true /* TODO */);
} else {
- previousSurfaceControl = mSurfaceControl;
- mSurfaceControl = builder
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFlags(mSurfaceFlags)
- .setFormat(mFormat)
- .build();
- mBlastSurfaceControl = null;
- mBlastBufferQueue = null;
+ // update blast layer
+ mTmpTransaction
+ .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+ .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
+ .show(mBlastSurfaceControl)
+ .apply();
}
- mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
- .setName("Background for " + name)
- .setLocalOwnerView(this)
- .setOpaque(true)
- .setColorLayer()
- .setParent(mSurfaceControl)
- .setCallsite("SurfaceView.updateSurface")
- .build();
- return previousSurfaceControl;
+ if (mBackgroundControl == null) {
+ mBackgroundControl = createBackgroundControl(name);
+ }
+
+ // Always recreate the IGBP for compatibility. This can be optimized in the future but
+ // the behavior change will need to be gated by SDK version.
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ }
+ mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight, mFormat, true /* TODO */);
}
private void onDrawFinished() {
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 4716141..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) {
@@ -1898,11 +1899,12 @@
}
private void setBoundsLayerCrop(Transaction t) {
- // mWinFrame is already adjusted for surface insets. So offset it and use it as
- // the cropping bounds.
- mTempBoundsRect.set(mWinFrame);
- mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
- mWindowAttributes.surfaceInsets.top);
+ // Adjust of insets and update the bounds layer so child surfaces do not draw into
+ // the surface inset region.
+ mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+ mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+ mWindowAttributes.surfaceInsets.top,
+ mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
}
@@ -1913,25 +1915,18 @@
private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
if (mBoundsLayer != null) {
setBoundsLayerCrop(t);
- t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(),
- mSurface.getNextFrameNumber());
return true;
}
return false;
}
- private void prepareSurfaces(boolean sizeChanged) {
+ private void prepareSurfaces() {
final SurfaceControl.Transaction t = mTransaction;
final SurfaceControl sc = getSurfaceControl();
if (!sc.isValid()) return;
- boolean applyTransaction = updateBoundsLayer(t);
- if (sizeChanged) {
- applyTransaction = true;
- t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
- }
- if (applyTransaction) {
- t.apply();
+ if (updateBoundsLayer(t)) {
+ mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
}
}
@@ -1947,6 +1942,10 @@
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
}
+
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+ }
}
/**
@@ -2842,8 +2841,7 @@
mScroller.abortAnimation();
}
// Our surface is gone
- if (mAttachInfo.mThreadedRenderer != null &&
- mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (isHardwareEnabled()) {
mAttachInfo.mThreadedRenderer.destroy();
}
} else if ((surfaceReplaced
@@ -3036,7 +3034,7 @@
// stopping, but on the client side it doesn't get stopped since it's restarted quick
// enough. WMS doesn't want to keep around old children since they will leak when the
// client creates new children.
- prepareSurfaces(surfaceSizeChanged);
+ prepareSurfaces();
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3070,8 +3068,10 @@
// via the WM relayout code path. We probably eventually
// want to synchronize transparent region hint changes
// with draws.
- mTransaction.setTransparentRegionHint(getSurfaceControl(),
- mTransparentRegion).apply();
+ SurfaceControl sc = getSurfaceControl();
+ if (sc.isValid()) {
+ mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
+ }
}
}
@@ -3927,8 +3927,15 @@
};
}
+ /**
+ * @hide
+ */
+ public boolean isHardwareEnabled() {
+ return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
+ }
+
private boolean addFrameCompleteCallbackIfNeeded() {
- if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (!isHardwareEnabled()) {
return false;
}
@@ -4272,7 +4279,7 @@
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
- if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (isHardwareEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
@@ -7649,6 +7656,9 @@
mSurface.transferFrom(blastSurface);
}
}
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+ }
} else {
destroySurface();
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af18293..4ecdd78 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1703,6 +1703,30 @@
public abstract void setBackgroundDrawable(Drawable drawable);
/**
+ * Blurs the screen behind the window within the bounds of the window.
+ *
+ * The density of the blur is set by the blur radius. The radius defines the size
+ * of the neighbouring area, from which pixels will be averaged to form the final
+ * color for each pixel. The operation approximates a Gaussian blur.
+ * A radius of 0 means no blur. The higher the radius, the denser the blur.
+ *
+ * The window background drawable is drawn on top of the blurred region. The blur
+ * region bounds and rounded corners will mimic those of the background drawable.
+ *
+ * For the blur region to be visible, the window has to be translucent. See
+ * {@link android.R.styleable#Window_windowIsTranslucent}.
+ *
+ * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius},
+ * which blurs the whole screen behind the window. Background blur blurs the screen behind
+ * only within the bounds of the window.
+ *
+ * @param blurRadius The blur radius to use for window background blur in pixels
+ *
+ * @see android.R.styleable#Window_windowBackgroundBlurRadius
+ */
+ public void setBackgroundBlurRadius(int blurRadius) {}
+
+ /**
* Set the value for a drawable feature of this window, from a resource
* identifier. You must have called requestFeature(featureId) before
* calling this function.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 90c8e17..7b2bb73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,7 +115,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
@@ -1083,19 +1082,6 @@
}
}
- private static class ImeThreadFactory implements ThreadFactory {
- private final String mThreadName;
-
- ImeThreadFactory(String name) {
- mThreadName = name;
- }
-
- @Override
- public Thread newThread(Runnable r) {
- return new Thread(r, mThreadName);
- }
- }
-
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 61ff36c..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -18,15 +18,21 @@
import android.annotation.ColorInt;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
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;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -111,11 +117,14 @@
private float mGlowAlpha;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private float mGlowScaleY;
+ private float mDistance;
private float mGlowAlphaStart;
private float mGlowAlphaFinish;
private float mGlowScaleYStart;
private float mGlowScaleYFinish;
+ private float mDistanceStart;
+ private float mDistanceFinish;
private long mStartTime;
private float mDuration;
@@ -144,15 +153,26 @@
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.
* @param context Context used to provide theming and resource information for the EdgeEffect
*/
public EdgeEffect(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new EdgeEffect with a theme appropriate for the provided context.
+ * @param context Context used to provide theming and resource information for the EdgeEffect
+ * @param attrs The attributes of the XML tag that is inflating the view
+ */
+ public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
mPaint.setAntiAlias(true);
final TypedArray a = context.obtainStyledAttributes(
- com.android.internal.R.styleable.EdgeEffect);
+ attrs, com.android.internal.R.styleable.EdgeEffect);
final int themeColor = a.getColor(
com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
mEdgeEffectType = a.getInt(
@@ -236,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;
@@ -248,6 +275,7 @@
mDuration = PULL_TIME;
mPullDistance += deltaDistance;
+ mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
final float absdd = Math.abs(deltaDistance);
mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -267,6 +295,65 @@
}
/**
+ * A view should call this when content is pulled away from an edge by the user.
+ * This will update the state of the current visual effect and its associated animation.
+ * The host view should always {@link android.view.View#invalidate()} after this
+ * and draw the results accordingly. This works similarly to {@link #onPull(float, float)},
+ * but returns the amount of <code>deltaDistance</code> that has been consumed. If the
+ * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this
+ * function will return 0 and the drawn value will remain unchanged.
+ *
+ * This method can be used to reverse the effect from a pull or absorb and partially consume
+ * some of a motion:
+ *
+ * <pre class="prettyprint">
+ * if (deltaY < 0) {
+ * float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth());
+ * deltaY -= consumed * getHeight();
+ * if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease();
+ * }
+ * </pre>
+ *
+ * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+ * 1.f (full length of the view) or negative values to express change
+ * back toward the edge reached to initiate the effect.
+ * @param displacement The displacement from the starting side of the effect of the point
+ * initiating the pull. In the case of touch this is the finger position.
+ * Values may be from 0-1.
+ * @return The amount of <code>deltaDistance</code> that was consumed, a number between
+ * 0 and <code>deltaDistance</code>.
+ */
+ 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;
+ }
+
+ /**
+ * Returns the pull distance needed to be released to remove the showing effect.
+ * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and
+ * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}.
+ *
+ * This can be used in conjunction with {@link #onPullDistance(float, float)} to
+ * release the currently showing effect.
+ *
+ * @return The pull distance that must be released to remove the showing effect.
+ */
+ public float getDistance() {
+ return mDistance;
+ }
+
+ /**
* Call when the object is released after being pulled.
* This will begin the "decay" phase of the effect. After calling this method
* the host view should {@link android.view.View#invalidate()} and thereby
@@ -282,9 +369,11 @@
mState = STATE_RECEDE;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = RECEDE_TIME;
@@ -311,7 +400,7 @@
// nearly invisible.
mGlowAlphaStart = GLOW_ALPHA_START;
mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
+ mDistanceStart = mDistance;
// Growth for the size of the glow should be quadratic to properly
// respond
@@ -322,6 +411,9 @@
mGlowAlphaFinish = Math.max(
mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
mTargetDisplacement = 0.5f;
+
+ // Use glow values to estimate the absorption for stretch distance.
+ mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
}
/**
@@ -396,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;
}
@@ -447,6 +565,7 @@
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+ mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
if (t >= 1.f - EPSILON) {
@@ -458,10 +577,12 @@
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
// After absorb, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
@@ -470,10 +591,12 @@
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
// After pull, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
break;
case STATE_PULL_DECAY:
mState = STATE_RECEDE;
@@ -484,4 +607,20 @@
}
}
}
+
+ /**
+ * @return The estimated pull distance as calculated from mGlowScaleY.
+ */
+ private float calculateDistanceFromGlowValues(float scale, float alpha) {
+ if (scale >= 1f) {
+ // It should asymptotically approach 1, but not reach there.
+ // Here, we're just choosing a value that is large.
+ return 1f;
+ }
+ if (scale > 0f) {
+ float v = 1f / 0.7f / (mGlowScaleY - 1f);
+ return v * v / mBounds.height();
+ }
+ return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
+ }
}
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/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/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 794b642..d59a415 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -69,9 +69,7 @@
private final TextView mTextView;
SpellCheckerSession mSpellCheckerSession;
- // We assume that the sentence level spell check will always provide better results than words.
- // Although word SC has a sequential option.
- private boolean mIsSentenceSpellCheckSupported;
+
final int mCookie;
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -134,7 +132,6 @@
| SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
| SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
| SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS);
- mIsSentenceSpellCheckSupported = true;
}
// Restore SpellCheckSpans in pool
@@ -318,13 +315,11 @@
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
isEditing = false;
- } else if (mIsSentenceSpellCheckSupported) {
+ } else {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
isEditing = selectionEnd <= start || selectionStart > end;
- } else {
- isEditing = selectionEnd < start || selectionStart > end;
}
if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
@@ -346,13 +341,8 @@
textInfos = textInfosCopy;
}
- if (mIsSentenceSpellCheckSupported) {
- mSpellCheckerSession.getSentenceSuggestions(
- textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
- } else {
- mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
- false /* TODO Set sequentialWords to true for initial spell check */);
- }
+ mSpellCheckerSession.getSentenceSuggestions(
+ textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
}
}
@@ -381,32 +371,30 @@
editable, suggestionsInfo, spellCheckSpan, offset, length);
} else {
// Valid word -- isInDictionary || !looksLikeTypo
- if (mIsSentenceSpellCheckSupported) {
- // Allow the spell checker to remove existing misspelled span by
- // overwriting the span over the same place
- final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
- final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
- final int start;
- final int end;
- if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
- start = spellCheckSpanStart + offset;
- end = start + length;
- } else {
- start = spellCheckSpanStart;
- end = spellCheckSpanEnd;
- }
- if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
- && end > start) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Remove existing misspelled span. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- mSuggestionSpanCache.remove(key);
+ // Allow the spell checker to remove existing misspelled span by
+ // overwriting the span over the same place
+ final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
+ final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
+ final int start;
+ final int end;
+ if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
+ start = spellCheckSpanStart + offset;
+ end = start + length;
+ } else {
+ start = spellCheckSpanStart;
+ end = spellCheckSpanEnd;
+ }
+ if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
+ && end > start) {
+ final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+ final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+ if (tempSuggestionSpan != null) {
+ if (DBG) {
+ Log.i(TAG, "Remove existing misspelled span. "
+ + editable.subSequence(start, end));
}
+ editable.removeSpan(tempSuggestionSpan);
+ mSuggestionSpanCache.remove(key);
}
}
}
@@ -531,20 +519,16 @@
}
SuggestionSpan suggestionSpan =
new SuggestionSpan(mTextView.getContext(), suggestions, flags);
- // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
- // to share the logic of word level spell checker and sentence level spell checker
- if (mIsSentenceSpellCheckSupported) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Cached span on the same position is cleard. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
+ final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+ final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+ if (tempSuggestionSpan != null) {
+ if (DBG) {
+ Log.i(TAG, "Cached span on the same position is cleard. "
+ + editable.subSequence(start, end));
}
- mSuggestionSpanCache.put(key, suggestionSpan);
+ editable.removeSpan(tempSuggestionSpan);
}
+ mSuggestionSpanCache.put(key, suggestionSpan);
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
@@ -599,15 +583,8 @@
public void parse() {
Editable editable = (Editable) mTextView.getText();
// Iterate over the newly added text and schedule new SpellCheckSpans
- final int start;
- if (mIsSentenceSpellCheckSupported) {
- // TODO: Find the start position of the sentence.
- // Set span with the context
- start = Math.max(
- 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
- } else {
- start = editable.getSpanStart(mRange);
- }
+ final int start = Math.max(
+ 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
final int end = editable.getSpanEnd(mRange);
@@ -633,155 +610,80 @@
return;
}
- // We need to expand by one character because we want to include the spans that
- // end/start at position start/end respectively.
- SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1,
- SpellCheckSpan.class);
- SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
- SuggestionSpan.class);
-
- int wordCount = 0;
boolean scheduleOtherSpellCheck = false;
- if (mIsSentenceSpellCheckSupported) {
- if (wordIteratorWindowEnd < end) {
- if (DBG) {
- Log.i(TAG, "schedule other spell check.");
- }
- // Several batches needed on that region. Cut after last previous word
- scheduleOtherSpellCheck = true;
+ if (wordIteratorWindowEnd < end) {
+ if (DBG) {
+ Log.i(TAG, "schedule other spell check.");
}
- int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
- boolean correct = spellCheckEnd != BreakIterator.DONE;
- if (correct) {
- spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
- correct = spellCheckEnd != BreakIterator.DONE;
- }
- if (!correct) {
- if (DBG) {
- Log.i(TAG, "Incorrect range span.");
- }
- stop();
- return;
- }
- do {
- // TODO: Find the start position of the sentence.
- int spellCheckStart = wordStart;
- boolean createSpellCheckSpan = true;
- // Cancel or merge overlapped spell check spans
- for (int i = 0; i < mLength; ++i) {
- final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
- if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
- continue;
- }
- final int spanStart = editable.getSpanStart(spellCheckSpan);
- final int spanEnd = editable.getSpanEnd(spellCheckSpan);
- if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
- // No need to merge
- continue;
- }
- if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
- // There is a completely overlapped spell check span
- // skip this span
- createSpellCheckSpan = false;
- if (DBG) {
- Log.i(TAG, "The range is overrapped. Skip spell check.");
- }
- break;
- }
- // This spellCheckSpan is replaced by the one we are creating
- editable.removeSpan(spellCheckSpan);
- spellCheckStart = Math.min(spanStart, spellCheckStart);
- spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
- }
-
- if (DBG) {
- Log.d(TAG, "addSpellCheckSpan: "
- + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
- + ", next = " + scheduleOtherSpellCheck + "\n"
- + editable.subSequence(spellCheckStart, spellCheckEnd));
- }
-
- // Stop spell checking when there are no characters in the range.
- if (spellCheckEnd < start) {
- break;
- }
- if (spellCheckEnd <= spellCheckStart) {
- Log.w(TAG, "Trying to spellcheck invalid region, from "
- + start + " to " + end);
- break;
- }
- if (createSpellCheckSpan) {
- addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
- }
- } while (false);
- wordStart = spellCheckEnd;
- } else {
- while (wordStart <= end) {
- if (wordEnd >= start && wordEnd > wordStart) {
- if (wordCount >= MAX_NUMBER_OF_WORDS) {
- scheduleOtherSpellCheck = true;
- break;
- }
- // A new word has been created across the interval boundaries with this
- // edit. The previous spans (that ended on start / started on end) are
- // not valid anymore and must be removed.
- if (wordStart < start && wordEnd > start) {
- removeSpansAt(editable, start, spellCheckSpans);
- removeSpansAt(editable, start, suggestionSpans);
- }
-
- if (wordStart < end && wordEnd > end) {
- removeSpansAt(editable, end, spellCheckSpans);
- removeSpansAt(editable, end, suggestionSpans);
- }
-
- // Do not create new boundary spans if they already exist
- boolean createSpellCheckSpan = true;
- if (wordEnd == start) {
- for (int i = 0; i < spellCheckSpans.length; i++) {
- final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
- if (spanEnd == start) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (wordStart == end) {
- for (int i = 0; i < spellCheckSpans.length; i++) {
- final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
- if (spanStart == end) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (createSpellCheckSpan) {
- addSpellCheckSpan(editable, wordStart, wordEnd);
- }
- wordCount++;
- }
-
- // iterate word by word
- int originalWordEnd = wordEnd;
- wordEnd = mWordIterator.following(wordEnd);
- if ((wordIteratorWindowEnd < end) &&
- (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
- wordIteratorWindowEnd =
- Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
- mWordIterator.setCharSequence(
- editable, originalWordEnd, wordIteratorWindowEnd);
- wordEnd = mWordIterator.following(originalWordEnd);
- }
- if (wordEnd == BreakIterator.DONE) break;
- wordStart = mWordIterator.getBeginning(wordEnd);
- if (wordStart == BreakIterator.DONE) {
- break;
- }
- }
+ // Several batches needed on that region. Cut after last previous word
+ scheduleOtherSpellCheck = true;
}
+ int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
+ boolean correct = spellCheckEnd != BreakIterator.DONE;
+ if (correct) {
+ spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
+ correct = spellCheckEnd != BreakIterator.DONE;
+ }
+ if (!correct) {
+ if (DBG) {
+ Log.i(TAG, "Incorrect range span.");
+ }
+ stop();
+ return;
+ }
+ do {
+ // TODO: Find the start position of the sentence.
+ int spellCheckStart = wordStart;
+ boolean createSpellCheckSpan = true;
+ // Cancel or merge overlapped spell check spans
+ for (int i = 0; i < mLength; ++i) {
+ final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
+ if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
+ continue;
+ }
+ final int spanStart = editable.getSpanStart(spellCheckSpan);
+ final int spanEnd = editable.getSpanEnd(spellCheckSpan);
+ if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
+ // No need to merge
+ continue;
+ }
+ if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
+ // There is a completely overlapped spell check span
+ // skip this span
+ createSpellCheckSpan = false;
+ if (DBG) {
+ Log.i(TAG, "The range is overrapped. Skip spell check.");
+ }
+ break;
+ }
+ // This spellCheckSpan is replaced by the one we are creating
+ editable.removeSpan(spellCheckSpan);
+ spellCheckStart = Math.min(spanStart, spellCheckStart);
+ spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
+ }
+
+ if (DBG) {
+ Log.d(TAG, "addSpellCheckSpan: "
+ + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
+ + ", next = " + scheduleOtherSpellCheck + "\n"
+ + editable.subSequence(spellCheckStart, spellCheckEnd));
+ }
+
+ // Stop spell checking when there are no characters in the range.
+ if (spellCheckEnd < start) {
+ break;
+ }
+ if (spellCheckEnd <= spellCheckStart) {
+ Log.w(TAG, "Trying to spellcheck invalid region, from "
+ + start + " to " + end);
+ break;
+ }
+ if (createSpellCheckSpan) {
+ addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
+ }
+ } while (false);
+ wordStart = spellCheckEnd;
if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
// Update range span: start new spell check from last wordStart
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe37c53..0f2089a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12940,17 +12940,11 @@
return false;
}
- final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
- final ClipDescription description = clipData.getDescription();
+ final ClipDescription description =
+ getClipboardManagerForUser().getPrimaryClipDescription();
final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
- final CharSequence text = clipData.getItemAt(0).getText();
- if (isPlainType && (text instanceof Spanned)) {
- Spanned spanned = (Spanned) text;
- if (TextUtils.hasStyleSpan(spanned)) {
- return true;
- }
- }
- return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
+ return (isPlainType && description.isStyledText())
+ || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
}
boolean canProcessText() {
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index b484dfa..2904a8c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -172,6 +172,22 @@
}
/**
+ * Update the LayoutParameters of the currently showing toast view. This is used for layout
+ * updates based on orientation changes.
+ */
+ public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin,
+ float verticalMargin, int gravity) {
+ checkState(mView != null, "Toast must be showing to update its layout parameters.");
+ Configuration config = mResources.getConfiguration();
+ mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
+ mParams.x = xOffset;
+ mParams.y = yOffset;
+ mParams.horizontalMargin = horizontalMargin;
+ mParams.verticalMargin = verticalMargin;
+ addToastView();
+ }
+
+ /**
* Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
* packageName} is a cross-user package.
*
@@ -221,18 +237,7 @@
adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
horizontalMargin, verticalMargin, removeWindowAnimations);
- if (mView.getParent() != null) {
- mWindowManager.removeView(mView);
- }
- try {
- mWindowManager.addView(mView, mParams);
- } catch (WindowManager.BadTokenException e) {
- // Since the notification manager service cancels the token right after it notifies us
- // to cancel the toast there is an inherent race and we may attempt to add a window
- // after the token has been invalidated. Let us hedge against that.
- Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
- return;
- }
+ addToastView();
trySendAccessibilityEvent(mView, mPackageName);
if (callback != null) {
try {
@@ -288,4 +293,19 @@
view.dispatchPopulateAccessibilityEvent(event);
mAccessibilityManager.sendAccessibilityEvent(event);
}
+
+ private void addToastView() {
+ if (mView.getParent() != null) {
+ mWindowManager.removeView(mView);
+ }
+ try {
+ mWindowManager.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ // Since the notification manager service cancels the token right after it notifies us
+ // to cancel the toast there is an inherent race and we may attempt to add a window
+ // after the token has been invalidated. Let us hedge against that.
+ Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+ return;
+ }
+ }
}
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 88b2257..8f541d0 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -42,6 +42,11 @@
void removeStartingWindow(int taskId);
/**
+ * Called when the Task want to copy the splash screen.
+ */
+ void copySplashScreenView(int taskId);
+
+ /**
* A callback when the Task is available for the registered organizer. The client is responsible
* for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
* be hidden so it is up to the organizer to show this task.
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
new file mode 100644
index 0000000..4b88a9b
--- /dev/null
+++ b/core/java/android/window/SplashScreen.java
@@ -0,0 +1,188 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Singleton;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * The interface that apps use to talk to the splash screen.
+ * <p>
+ * Each splash screen instance is bound to a particular {@link Activity}.
+ * To obtain a {@link SplashScreen} for an Activity, use
+ * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p>
+ */
+public interface SplashScreen {
+ /**
+ * <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its
+ * own. Normally the splash screen will show on screen before the content of the activity has
+ * been drawn, and disappear when the activity is showing on the screen. With this listener set,
+ * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if
+ * splash screen is showed, then the activity can create its own exit animation based on the
+ * SplashScreenView.</p>
+ *
+ * <p> Note that this method must be called before splash screen leave, so it only takes effect
+ * during or before {@link Activity#onResume}.</p>
+ *
+ * @param listener the listener for receive the splash screen with
+ *
+ * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView)
+ */
+ @SuppressLint("ExecutorRegistration")
+ void setOnExitAnimationListener(@Nullable SplashScreen.OnExitAnimationListener listener);
+
+ /**
+ * Listens for the splash screen exit event.
+ */
+ interface OnExitAnimationListener {
+ /**
+ * When receiving this callback, the {@link SplashScreenView} object will be drawing on top
+ * of the activity. The {@link SplashScreenView} represents the splash screen view
+ * object, developer can make an exit animation based on this view.</p>
+ *
+ * <p>If {@link SplashScreenView#remove} is not called after 5000ms, the method will be
+ * automatically called and the splash screen removed.</p>
+ *
+ * <p>This method is never invoked if your activity sets
+ * {@link #setOnExitAnimationListener} to <code>null</code>..
+ *
+ * @param view The view object which on top of this Activity.
+ * @see #setOnExitAnimationListener
+ */
+ void onSplashScreenExit(@NonNull SplashScreenView view);
+ }
+
+ /**
+ * @hide
+ */
+ class SplashScreenImpl implements SplashScreen {
+ private OnExitAnimationListener mExitAnimationListener;
+ private final IBinder mActivityToken;
+ private final SplashScreenManagerGlobal mGlobal;
+
+ public SplashScreenImpl(Context context) {
+ mActivityToken = context.getActivityToken();
+ mGlobal = SplashScreenManagerGlobal.getInstance();
+ }
+
+ @Override
+ public void setOnExitAnimationListener(
+ @Nullable SplashScreen.OnExitAnimationListener listener) {
+ if (mActivityToken == null) {
+ // This is not an activity.
+ return;
+ }
+ synchronized (mGlobal.mGlobalLock) {
+ mExitAnimationListener = listener;
+ if (listener != null) {
+ mGlobal.addImpl(this);
+ } else {
+ mGlobal.removeImpl(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * This class is only used internally to manage the activities for this process.
+ *
+ * @hide
+ */
+ class SplashScreenManagerGlobal {
+ private static final String TAG = SplashScreen.class.getSimpleName();
+ private final Object mGlobalLock = new Object();
+ private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();
+
+ private SplashScreenManagerGlobal() {
+ ActivityThread.currentActivityThread().registerSplashScreenManager(this);
+ }
+
+ public static SplashScreenManagerGlobal getInstance() {
+ return sInstance.get();
+ }
+
+ private static final Singleton<SplashScreenManagerGlobal> sInstance =
+ new Singleton<SplashScreenManagerGlobal>() {
+ @Override
+ protected SplashScreenManagerGlobal create() {
+ return new SplashScreenManagerGlobal();
+ }
+ };
+
+ private void addImpl(SplashScreenImpl impl) {
+ synchronized (mGlobalLock) {
+ mImpls.add(impl);
+ }
+ }
+
+ private void removeImpl(SplashScreenImpl impl) {
+ synchronized (mGlobalLock) {
+ mImpls.remove(impl);
+ }
+ }
+
+ private SplashScreenImpl findImpl(IBinder token) {
+ synchronized (mGlobalLock) {
+ for (SplashScreenImpl impl : mImpls) {
+ if (impl.mActivityToken == token) {
+ return impl;
+ }
+ }
+ }
+ return null;
+ }
+
+ public void tokenDestroyed(IBinder token) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ if (impl != null) {
+ removeImpl(impl);
+ }
+ }
+ }
+
+ public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ if (impl == null) {
+ return;
+ }
+ if (impl.mExitAnimationListener == null) {
+ Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token);
+ return;
+ }
+ impl.mExitAnimationListener.onSplashScreenExit(view);
+ }
+ }
+
+ public boolean containsExitListener(IBinder token) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ return impl != null && impl.mExitAnimationListener != null;
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/window/SplashScreenView.aidl
similarity index 78%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/window/SplashScreenView.aidl
index 14d57bf..cc7ac1e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/window/SplashScreenView.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.window;
-parcelable ExternalTimeSuggestion;
+/** @hide */
+parcelable SplashScreenView.SplashScreenViewParcelable;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
new file mode 100644
index 0000000..35ccfca
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.java
@@ -0,0 +1,510 @@
+/*
+ * 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.window;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.policy.DecorView;
+
+/**
+ * <p>The view which allows an activity to customize its splash screen exit animation.</p>
+ *
+ * <p>Activities will receive this view as a parameter of
+ * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if
+ * they set {@link SplashScreen#setOnExitAnimationListener}.
+ * When this callback is called, this view will be on top of the activity.</p>
+ *
+ * <p>This view is composed of a view containing the splashscreen icon (see
+ * windowSplashscreenAnimatedIcon) and a background.
+ * Developers can use {@link #getIconView} to get this view and replace the drawable or
+ * add animation to it. The background of this view is filled with a single color, which can be
+ * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p>
+ *
+ * @see SplashScreen
+ */
+public final class SplashScreenView extends FrameLayout {
+ private static final String TAG = SplashScreenView.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private boolean mNotCopyable;
+ private int mInitBackgroundColor;
+ private View mIconView;
+ private Bitmap mParceledIconBitmap;
+ private View mBrandingImageView;
+ private Bitmap mParceledBrandingBitmap;
+ private long mIconAnimationDuration;
+ private long mIconAnimationStart;
+
+ private Animatable mAnimatableIcon;
+ private ValueAnimator mAnimator;
+
+ // cache original window and status
+ private Window mWindow;
+ private boolean mDrawBarBackground;
+ private int mStatusBarColor;
+ private int mNavigationBarColor;
+
+ /**
+ * Internal builder to create a SplashScreenWindowView object.
+ * @hide
+ */
+ public static class Builder {
+ private final Context mContext;
+ private int mIconSize;
+ private @ColorInt int mBackgroundColor;
+ private Bitmap mParceledIconBitmap;
+ private Drawable mIconDrawable;
+ private int mBrandingImageWidth;
+ private int mBrandingImageHeight;
+ private Drawable mBrandingDrawable;
+ private Bitmap mParceledBrandingBitmap;
+ private long mIconAnimationStart;
+ private long mIconAnimationDuration;
+
+ public Builder(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so
+ * you do not need to call other set methods.
+ */
+ public Builder createFromParcel(SplashScreenViewParcelable parcelable) {
+ mIconSize = parcelable.getIconSize();
+ mBackgroundColor = parcelable.getBackgroundColor();
+ if (parcelable.mIconBitmap != null) {
+ mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap);
+ mParceledIconBitmap = parcelable.mIconBitmap;
+ }
+ if (parcelable.mBrandingBitmap != null) {
+ setBrandingDrawable(new BitmapDrawable(mContext.getResources(),
+ parcelable.mBrandingBitmap), parcelable.mBrandingWidth,
+ parcelable.mBrandingHeight);
+ mParceledBrandingBitmap = parcelable.mBrandingBitmap;
+ }
+ mIconAnimationStart = parcelable.mIconAnimationStart;
+ mIconAnimationDuration = parcelable.mIconAnimationDuration;
+ return this;
+ }
+
+ /**
+ * Set the rectangle size for the center view.
+ */
+ public Builder setIconSize(int iconSize) {
+ mIconSize = iconSize;
+ return this;
+ }
+
+ /**
+ * Set the background color for the view.
+ */
+ public Builder setBackgroundColor(@ColorInt int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ return this;
+ }
+
+ /**
+ * Set the Drawable object to fill the center view.
+ */
+ public Builder setCenterViewDrawable(Drawable drawable) {
+ mIconDrawable = drawable;
+ return this;
+ }
+
+ /**
+ * Set the animation duration if icon is animatable.
+ */
+ public Builder setAnimationDuration(int duration) {
+ mIconAnimationDuration = duration;
+ return this;
+ }
+
+ /**
+ * Set the Drawable object and size for the branding view.
+ */
+ public Builder setBrandingDrawable(Drawable branding, int width, int height) {
+ mBrandingDrawable = branding;
+ mBrandingImageWidth = width;
+ mBrandingImageHeight = height;
+ return this;
+ }
+
+ /**
+ * Create SplashScreenWindowView object from materials.
+ */
+ public SplashScreenView build() {
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ final SplashScreenView view = (SplashScreenView)
+ layoutInflater.inflate(R.layout.splash_screen_view, null, false);
+ view.mInitBackgroundColor = mBackgroundColor;
+ view.setBackgroundColor(mBackgroundColor);
+ view.mIconView = view.findViewById(R.id.splashscreen_icon_view);
+ view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
+ // center icon
+ if (mIconSize != 0) {
+ final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams();
+ params.width = mIconSize;
+ params.height = mIconSize;
+ view.mIconView.setLayoutParams(params);
+ }
+ if (mIconDrawable != null) {
+ view.mIconView.setBackground(mIconDrawable);
+ view.initIconAnimation(mIconDrawable, mIconAnimationDuration);
+ }
+ view.mIconAnimationStart = mIconAnimationStart;
+ view.mIconAnimationDuration = mIconAnimationDuration;
+ if (mParceledIconBitmap != null) {
+ view.mParceledIconBitmap = mParceledIconBitmap;
+ }
+ // branding image
+ if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+ final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
+ params.width = mBrandingImageWidth;
+ params.height = mBrandingImageHeight;
+ view.mBrandingImageView.setLayoutParams(params);
+ }
+ if (mBrandingDrawable != null) {
+ view.mBrandingImageView.setBackground(mBrandingDrawable);
+ }
+ if (mParceledBrandingBitmap != null) {
+ view.mParceledBrandingBitmap = mParceledBrandingBitmap;
+ }
+ if (DEBUG) {
+ Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: "
+ + mIconDrawable + " size: " + mIconSize + "\n Branding: view: "
+ + view.mBrandingImageView + " drawable: " + mBrandingDrawable
+ + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
+ }
+ return view;
+ }
+ }
+
+ /** @hide */
+ public SplashScreenView(Context context) {
+ super(context);
+ }
+
+ /** @hide */
+ public SplashScreenView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ /**
+ * Declared this view is not copyable.
+ * @hide
+ */
+ public void setNotCopyable() {
+ mNotCopyable = true;
+ }
+
+ /**
+ * Whether this view is copyable.
+ * @hide
+ */
+ public boolean isCopyable() {
+ return !mNotCopyable;
+ }
+
+ /**
+ * Returns the duration of the icon animation if icon is animatable.
+ *
+ * @see android.R.attr#windowSplashScreenAnimatedIcon
+ * @see android.R.attr#windowSplashScreenAnimationDuration
+ */
+ public long getIconAnimationDurationMillis() {
+ return mIconAnimationDuration;
+ }
+
+ /**
+ * If the replaced icon is animatable, return the animation start time in millisecond based on
+ * system. The start time is set using {@link SystemClock#uptimeMillis()}.
+ */
+ public long getIconAnimationStartMillis() {
+ return mIconAnimationStart;
+ }
+
+ void initIconAnimation(Drawable iconDrawable, long duration) {
+ if (iconDrawable instanceof Animatable) {
+ mAnimatableIcon = (Animatable) iconDrawable;
+ mAnimator = ValueAnimator.ofInt(0, 1);
+ mAnimator.setDuration(duration);
+ mAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIconAnimationStart = SystemClock.uptimeMillis();
+ mAnimatableIcon.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // do not repeat
+ mAnimatableIcon.stop();
+ }
+ });
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void startIntroAnimation() {
+ if (mAnimatableIcon != null) {
+ mAnimator.start();
+ }
+ }
+
+ /**
+ * <p>Remove this view and release its resource. </p>
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ */
+ public void remove() {
+ setVisibility(GONE);
+ if (mParceledIconBitmap != null) {
+ mIconView.setBackground(null);
+ mParceledIconBitmap.recycle();
+ mParceledIconBitmap = null;
+ }
+ if (mParceledBrandingBitmap != null) {
+ mBrandingImageView.setBackground(null);
+ mParceledBrandingBitmap.recycle();
+ mParceledBrandingBitmap = null;
+ }
+ if (mWindow != null) {
+ final DecorView decorView = (DecorView) mWindow.peekDecorView();
+ if (DEBUG) {
+ Log.d(TAG, "remove starting view");
+ }
+ if (decorView != null) {
+ decorView.removeView(this);
+ }
+ restoreSystemUIColors();
+ mWindow = null;
+ }
+ }
+
+ /**
+ * Cache the root window.
+ * @hide
+ */
+ public void cacheRootWindow(Window window) {
+ mWindow = window;
+ }
+
+ /**
+ * Called after SplashScreenView has added on the root window.
+ * @hide
+ */
+ public void makeSystemUIColorsTransparent() {
+ if (mWindow != null) {
+ final WindowManager.LayoutParams attr = mWindow.getAttributes();
+ mDrawBarBackground = (attr.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ mWindow.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ mStatusBarColor = mWindow.getStatusBarColor();
+ mNavigationBarColor = mWindow.getNavigationBarDividerColor();
+ mWindow.setStatusBarColor(Color.TRANSPARENT);
+ mWindow.setNavigationBarColor(Color.TRANSPARENT);
+ }
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
+
+ private void restoreSystemUIColors() {
+ if (mWindow != null) {
+ if (!mDrawBarBackground) {
+ mWindow.clearFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ }
+ mWindow.setStatusBarColor(mStatusBarColor);
+ mWindow.setNavigationBarColor(mNavigationBarColor);
+ }
+ }
+
+ /**
+ * Get the view containing the Splash Screen icon and its background.
+ * @see android.R.attr#windowSplashScreenAnimatedIcon
+ */
+ public @Nullable View getIconView() {
+ return mIconView;
+ }
+
+ /**
+ * Get the branding image view.
+ * @hide
+ */
+ @TestApi
+ public @Nullable View getBrandingView() {
+ return mBrandingImageView;
+ }
+
+ /**
+ * Get the initial background color of this view.
+ * @hide
+ */
+ @ColorInt int getInitBackgroundColor() {
+ return mInitBackgroundColor;
+ }
+
+ /**
+ * Use to create {@link SplashScreenView} object across process.
+ * @hide
+ */
+ public static class SplashScreenViewParcelable implements Parcelable {
+ private int mIconSize;
+ private int mBackgroundColor;
+
+ private Bitmap mIconBitmap;
+ private int mBrandingWidth;
+ private int mBrandingHeight;
+ private Bitmap mBrandingBitmap;
+
+ private long mIconAnimationStart;
+ private long mIconAnimationDuration;
+
+ public SplashScreenViewParcelable(SplashScreenView view) {
+ ViewGroup.LayoutParams params = view.getIconView().getLayoutParams();
+ mIconSize = params.height;
+ mBackgroundColor = view.getInitBackgroundColor();
+
+ mIconBitmap = copyDrawable(view.getIconView().getBackground());
+ mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
+ params = view.getBrandingView().getLayoutParams();
+ mBrandingWidth = params.width;
+ mBrandingHeight = params.height;
+
+ mIconAnimationStart = view.getIconAnimationStartMillis();
+ mIconAnimationDuration = view.getIconAnimationDurationMillis();
+ }
+
+ private Bitmap copyDrawable(Drawable drawable) {
+ if (drawable != null) {
+ final Rect initialBounds = drawable.copyBounds();
+ final int width = initialBounds.width();
+ final int height = initialBounds.height();
+
+ final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas bmpCanvas = new Canvas(snapshot);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(bmpCanvas);
+ final Bitmap copyBitmap = snapshot.createAshmemBitmap();
+ snapshot.recycle();
+ return copyBitmap;
+ }
+ return null;
+ }
+
+ private SplashScreenViewParcelable(@NonNull Parcel source) {
+ readParcel(source);
+ }
+
+ private void readParcel(@NonNull Parcel source) {
+ mIconSize = source.readInt();
+ mBackgroundColor = source.readInt();
+ mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
+ mBrandingWidth = source.readInt();
+ mBrandingHeight = source.readInt();
+ mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
+ mIconAnimationStart = source.readLong();
+ mIconAnimationDuration = source.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mIconSize);
+ dest.writeInt(mBackgroundColor);
+ dest.writeTypedObject(mIconBitmap, flags);
+ dest.writeInt(mBrandingWidth);
+ dest.writeInt(mBrandingHeight);
+ dest.writeTypedObject(mBrandingBitmap, flags);
+ dest.writeLong(mIconAnimationStart);
+ dest.writeLong(mIconAnimationDuration);
+ }
+
+ public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
+ new Parcelable.Creator<SplashScreenViewParcelable>() {
+ public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
+ return new SplashScreenViewParcelable(source);
+ }
+ public SplashScreenViewParcelable[] newArray(int size) {
+ return new SplashScreenViewParcelable[size];
+ }
+ };
+
+ /**
+ * Release the bitmap if another process cannot handle it.
+ */
+ public void clearIfNeeded() {
+ if (mIconBitmap != null) {
+ mIconBitmap.recycle();
+ mIconBitmap = null;
+ }
+ if (mBrandingBitmap != null) {
+ mBrandingBitmap.recycle();
+ mBrandingBitmap = null;
+ }
+ }
+
+ int getIconSize() {
+ return mIconSize;
+ }
+
+ int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+ }
+}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 2282cc5..63b9e9b 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -95,6 +95,12 @@
*/
public int startingWindowTypeParameter;
+ /**
+ * Specifies a theme for the splash screen.
+ * @hide
+ */
+ public int splashScreenThemeResId;
+
public StartingWindowInfo() {
}
@@ -115,6 +121,7 @@
dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
dest.writeTypedObject(mainWindowLayoutParams, flags);
+ dest.writeInt(splashScreenThemeResId);
}
void readFromParcel(@NonNull Parcel source) {
@@ -124,6 +131,7 @@
topOpaqueWindowLayoutParams = source.readTypedObject(
WindowManager.LayoutParams.CREATOR);
mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
+ splashScreenThemeResId = source.readInt();
}
@Override
@@ -135,7 +143,8 @@
+ Integer.toHexString(startingWindowTypeParameter)
+ " insetsState=" + topOpaqueWindowInsetsState
+ " topWindowLayoutParams=" + topOpaqueWindowLayoutParams
- + " mainWindowLayoutParams=" + mainWindowLayoutParams;
+ + " mainWindowLayoutParams=" + mainWindowLayoutParams
+ + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId);
}
public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index cdb4762..217ade8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -105,6 +105,12 @@
public void removeStartingWindow(int taskId) {}
/**
+ * Called when the Task want to copy the splash screen.
+ */
+ @BinderThread
+ public void copySplashScreenView(int taskId) {}
+
+ /**
* Called when a task with the registered windowing mode can be controlled by this task
* organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer
* to show this task.
@@ -223,6 +229,11 @@
}
@Override
+ public void copySplashScreenView(int taskId) {
+ mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId));
+ }
+
+ @Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
}
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index c2ee646..0152387 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -53,10 +53,8 @@
public static final String KEY_CUR_TASK = "cur_task";
/** Package of newly requested heavy-weight app. */
public static final String KEY_NEW_APP = "new_app";
- public static final String KEY_ACTIVITY_OPTIONS = "activity_options";
IntentSender mStartIntent;
- Bundle mActivityOptions;
boolean mHasResult;
String mCurApp;
int mCurTask;
@@ -67,9 +65,8 @@
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+
mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
- mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS);
mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
@@ -151,9 +148,9 @@
if (mHasResult) {
startIntentSenderForResult(mStartIntent, -1, null,
Intent.FLAG_ACTIVITY_FORWARD_RESULT,
- Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions);
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
} else {
- startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions);
+ startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
}
} catch (IntentSender.SendIntentException ex) {
Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
similarity index 88%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
index 14d57bf..5d02a29 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.internal.compat;
-parcelable ExternalTimeSuggestion;
+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/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 0ede1b8..e602cd2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -28,6 +28,7 @@
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.FileObserver;
@@ -504,7 +505,7 @@
final int pfdMode = ParcelFileDescriptor.parseMode(mode);
if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
- return ParcelFileDescriptor.open(file, pfdMode);
+ return openFileForRead(file);
} else {
try {
// When finished writing, kick off media scanner
@@ -519,6 +520,24 @@
}
}
+ private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException {
+ final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target);
+
+ // Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to
+ // transcode or not transcode can be made based upon the calling app's uid, and not based
+ // upon the Provider's uid.
+ final Bundle opts = new Bundle();
+ opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, Binder.getCallingUid());
+
+ final AssetFileDescriptor afd =
+ getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts);
+ if (afd == null) {
+ return null;
+ }
+
+ return afd.getParcelFileDescriptor();
+ }
+
/**
* Test if the file matches the query arguments.
*
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal;
+package com.android.internal.display;
import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e9bc84..cba6af9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -499,7 +499,7 @@
}
public String getPerfettoTrigger() {
- return String.format("interaction-jank-monitor-%d", mCujType);
+ return String.format("com.android.telemetry.interaction-jank-monitor-%d", mCujType);
}
public String getName() {
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index af61f91..4f2f973b 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -133,6 +133,12 @@
public double wakeLockPowerMah;
public double wifiPowerMah;
public double systemServiceCpuPowerMah;
+ public double[] customMeasuredPowerMah;
+
+ // Power that is re-attributed to other sippers. For example, for System Server
+ // this represents the power attributed to apps requesting system services.
+ // The value should be negative or zero.
+ public double powerReattributedToOtherSippersMah;
// Do not include this sipper in results because it is included
// in an aggregate sipper.
@@ -251,6 +257,18 @@
proportionalSmearMah += other.proportionalSmearMah;
totalSmearedPowerMah += other.totalSmearedPowerMah;
systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
+ if (other.customMeasuredPowerMah != null) {
+ if (customMeasuredPowerMah == null) {
+ customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length];
+ }
+ if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) {
+ // This should always be true.
+ for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) {
+ customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx];
+ }
+ }
+ }
+ powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah;
}
/**
@@ -264,6 +282,15 @@
sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
+ systemServiceCpuPowerMah;
+ if (customMeasuredPowerMah != null) {
+ for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) {
+ totalPowerMah += customMeasuredPowerMah[idx];
+ }
+ }
+
+ // powerAttributedToOtherSippersMah is negative or zero
+ totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah;
+
totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index aa5015a..b20f50d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -347,6 +347,7 @@
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+ mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
mPowerCalculators.add(new UserPowerCalculator());
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6e41b3f..87820a8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1002,8 +1002,12 @@
int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
/**
- * Accumulated energy consumption, that is not attributed to individual uids, of various
- * consumers while on battery.
+ * Accumulated global (generally, device-wide total) energy consumption of various consumers
+ * while on battery.
+ * Its '<b>custom</b> energy buckets' correspond to the
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
* If energy consumer data is completely unavailable this will be null.
*/
@GuardedBy("this")
@@ -7157,20 +7161,34 @@
@Override
public long getScreenOnEnergy() {
- if (mGlobalMeasuredEnergyStats == null) {
- return ENERGY_DATA_UNAVAILABLE;
- }
- return mGlobalMeasuredEnergyStats
- .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+ return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
}
@Override
public long getScreenDozeEnergy() {
+ return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+ }
+
+ /**
+ * Returns the energy in microjoules that the given standard energy bucket consumed.
+ * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+ *
+ * @param bucket standard energy bucket of interest
+ * @return energy (in microjoules) used for this energy bucket
+ */
+ private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
if (mGlobalMeasuredEnergyStats == null) {
return ENERGY_DATA_UNAVAILABLE;
}
- return mGlobalMeasuredEnergyStats
- .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+ return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
+ }
+
+ @Override
+ public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() {
+ if (mGlobalMeasuredEnergyStats == null) {
+ return null;
+ }
+ return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
}
@Override public long getStartClockTime() {
@@ -7519,9 +7537,16 @@
*/
private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
- /** Measured energies attributed to this uid while on battery. */
- // We do not use a SparseArray<LongSamplingCounters> since it would cause lots of
- // unnecessary timebase references, and we're just going to use on-battery anyway...
+ /**
+ * Measured energies attributed to this uid while on battery.
+ * Its '<b>custom</b> energy buckets' correspond to the
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * Will be null if energy consumer data is completely unavailable (in which case
+ * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid
+ * is 0 for every bucket.
+ */
private MeasuredEnergyStats mUidMeasuredEnergyStats;
/**
@@ -7941,6 +7966,13 @@
.updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
}
+ /** Adds the given energy to the given custom energy bucket for this uid. */
+ private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+ boolean accumulate) {
+ getOrCreateMeasuredEnergyStatsLocked()
+ .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+ }
+
/**
* Returns the energy used by this uid for a standard energy bucket of interest.
* @param bucket standard energy bucket of interest
@@ -7957,6 +7989,18 @@
return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
}
+ @Override
+ public long[] getCustomMeasuredEnergiesMicroJoules() {
+ if (mBsi.mGlobalMeasuredEnergyStats == null) {
+ return null;
+ }
+ if (mUidMeasuredEnergyStats == null) {
+ // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
+ return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()];
+ }
+ return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
+ }
+
/**
* Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
* since last marked. Also sets the mark time for both these timers.
@@ -12465,6 +12509,39 @@
}
/**
+ * Accumulate Custom energy bucket energy, globally and for each app.
+ *
+ * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+ * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+ * Data inside uidEnergies will not be modified (treated immutable).
+ */
+ public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+ long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+ + customEnergyBucket
+ + " with total energy " + totalEnergyUJ
+ + " and uid energies " + String.valueOf(uidEnergies));
+ }
+ if (mGlobalMeasuredEnergyStats == null) return;
+ if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+ mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+ if (uidEnergies == null) return;
+ final int numUids = uidEnergies.size();
+ for (int i = 0; i < numUids; i++) {
+ final int uidInt = mapUid(uidEnergies.keyAt(i));
+ final long uidEnergyUJ = uidEnergies.valueAt(i);
+ if (uidEnergyUJ == 0) continue;
+ // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this,
+ // or converse problem of not creating a new Uid if its first blame is recorded here.
+ final Uid uidObj = getUidStatsLocked(uidInt);
+ uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+ }
+ }
+
+ /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
@@ -12933,16 +13010,17 @@
mWakeLockAllocationsUs = null;
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidFreqTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.d(TAG, "Got freq readings for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
- mCpuUidFreqTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
@@ -13001,6 +13079,9 @@
}
}
});
+ for (int uid : uidsToRemove) {
+ mCpuUidFreqTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13047,21 +13128,25 @@
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidActiveTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.w(TAG, "Got active times for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
- mCpuUidActiveTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
});
+ for (int uid : uidsToRemove) {
+ mCpuUidActiveTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13077,21 +13162,25 @@
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidClusterTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.w(TAG, "Got cluster times for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
- mCpuUidClusterTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
});
+ for (int uid : uidsToRemove) {
+ mCpuUidClusterTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -14454,8 +14543,13 @@
*/
@GuardedBy("this")
public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
- if (mGlobalMeasuredEnergyStats == null) return;
- dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
+ pw.printf("On battery measured energy stats (microjoules) \n");
+ if (mGlobalMeasuredEnergyStats == null) {
+ pw.printf(" Not supported on this device.\n");
+ return;
+ }
+
+ dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats);
int size = mUidStats.size();
for (int i = 0; i < size; i++) {
@@ -14472,7 +14566,8 @@
MeasuredEnergyStats stats) {
if (stats == null) return;
final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ");
- iPw.printf("On battery measured energy stats for %s:\n", name);
+ iPw.increaseIndent();
+ iPw.printf("%s:\n", name);
iPw.increaseIndent();
stats.dump(iPw);
iPw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 094724c..233ba19 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -70,10 +70,14 @@
mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
-
+ mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
mPowerCalculators.add(new UserPowerCalculator());
+
+ // It is important that SystemServicePowerCalculator be applied last,
+ // because it re-attributes some of the power estimated by the other
+ // calculators.
+ mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
}
}
return mPowerCalculators;
@@ -127,7 +131,8 @@
final long uptimeUs = SystemClock.uptimeMillis() * 1000;
final List<PowerCalculator> powerCalculators = getPowerCalculators();
- for (PowerCalculator powerCalculator : powerCalculators) {
+ for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+ PowerCalculator powerCalculator = powerCalculators.get(i);
powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
query);
}
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
new file mode 100644
index 0000000..4babe8d
--- /dev/null
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
+ * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ */
+public class CustomMeasuredPowerCalculator extends PowerCalculator {
+ public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
+ }
+
+ @Override
+ protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ updateCustomMeasuredPowerMah(app, u.getCustomMeasuredEnergiesMicroJoules());
+ }
+
+ private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredEnergiesUJ) {
+ sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredEnergiesUJ);
+ }
+
+ private double[] calculateMeasuredEnergiesMah(long[] measuredEnergiesUJ) {
+ if (measuredEnergiesUJ == null) {
+ return null;
+ }
+ final double[] measuredEnergiesMah = new double[measuredEnergiesUJ.length];
+ for (int i = 0; i < measuredEnergiesUJ.length; i++) {
+ measuredEnergiesMah[i] = uJtoMah(measuredEnergiesUJ[i]);
+ }
+ return measuredEnergiesMah;
+ }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
similarity index 69%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/com/android/internal/os/KernelCpuBpfTracking.java
index 14d57bf..2852547 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.internal.os;
-parcelable ExternalTimeSuggestion;
+/** 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/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 955f6af..5c0eeb2 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -20,12 +20,12 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
-import java.util.Arrays;
import java.util.List;
/**
@@ -36,87 +36,112 @@
private static final boolean DEBUG = false;
private static final String TAG = "SystemServicePowerCalc";
- private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
-
- private final PowerProfile mPowerProfile;
-
- // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
- // Data organized like this:
+ // Power estimators per CPU cluster, per CPU frequency. The array is flattened according
+ // to this layout:
// {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
- private double[] mSystemServicePowerMaUs;
+ private final UsageBasedPowerEstimator[] mPowerEstimators;
public SystemServicePowerCalculator(PowerProfile powerProfile) {
- mPowerProfile = powerProfile;
+ int numFreqs = 0;
+ final int numCpuClusters = powerProfile.getNumCpuClusters();
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ }
+
+ mPowerEstimators = new UsageBasedPowerEstimator[numFreqs];
+ int index = 0;
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int speed = 0; speed < numSpeeds; speed++) {
+ mPowerEstimators[index++] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForCpuCore(cluster, speed));
+ }
+ }
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- calculateSystemServicePower(batteryStats);
- super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
- }
+ double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ final UidBatteryConsumer.Builder systemServerConsumer = uidBatteryConsumerBuilders.get(
+ Process.SYSTEM_UID);
- @Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
- calculateSystemServerCpuPowerMah(u));
+ if (systemServerConsumer != null) {
+ systemServicePowerMah = Math.min(systemServicePowerMah,
+ systemServerConsumer.getTotalPower());
+
+ // The system server power needs to be adjusted because part of it got
+ // distributed to applications
+ systemServerConsumer.setConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
+ -systemServicePowerMah);
+ }
+
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ if (app != systemServerConsumer) {
+ final BatteryStats.Uid uid = app.getBatteryStatsUid();
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+ systemServicePowerMah * uid.getProportionalSystemServiceUsage());
+ }
+ }
}
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType,
SparseArray<UserHandle> asUsers) {
- calculateSystemServicePower(batteryStats);
- super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+ double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+ BatterySipper systemServerSipper = null;
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ if (app.getUid() == Process.SYSTEM_UID) {
+ systemServerSipper = app;
+ break;
+ }
+ }
+ }
+
+ if (systemServerSipper != null) {
+ systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower());
+
+ // The system server power needs to be adjusted because part of it got
+ // distributed to applications
+ systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah;
+ }
+
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ if (app != systemServerSipper) {
+ final BatteryStats.Uid uid = app.uidObj;
+ app.systemServiceCpuPowerMah =
+ systemServicePowerMah * uid.getProportionalSystemServiceUsage();
+ }
+ }
+ }
}
- @Override
- protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
- app.systemServiceCpuPowerMah = calculateSystemServerCpuPowerMah(u);
- }
-
- private void calculateSystemServicePower(BatteryStats batteryStats) {
+ private double calculateSystemServicePower(BatteryStats batteryStats) {
final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds();
if (systemServiceTimeAtCpuSpeeds == null) {
- return;
+ return 0;
}
- if (mSystemServicePowerMaUs == null) {
- mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length];
- }
- int index = 0;
- final int numCpuClusters = mPowerProfile.getNumCpuClusters();
- for (int cluster = 0; cluster < numCpuClusters; cluster++) {
- final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
- for (int speed = 0; speed < numSpeeds; speed++) {
- mSystemServicePowerMaUs[index] =
- systemServiceTimeAtCpuSpeeds[index]
- * mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
- index++;
- }
+ // TODO(179210707): additionally account for CPU active and per cluster battery use
+
+ double powerMah = 0;
+ for (int i = 0; i < mPowerEstimators.length; i++) {
+ powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i]);
}
if (DEBUG) {
- Log.d(TAG, "System service power per CPU cluster and frequency:"
- + Arrays.toString(mSystemServicePowerMaUs));
+ Log.d(TAG, "System service power:" + powerMah);
}
- }
- private double calculateSystemServerCpuPowerMah(BatteryStats.Uid u) {
- double cpuPowerMaUs = 0;
- final double proportionalUsage = u.getProportionalSystemServiceUsage();
- if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) {
- for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
- cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
- }
- }
- return cpuPowerMaUs / MICROSEC_IN_HR;
- }
-
- @Override
- public void reset() {
- mSystemServicePowerMaUs = null;
+ return powerMah;
}
}
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 3f68597..0f4767b 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -15,7 +15,12 @@
*/
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
@@ -26,39 +31,93 @@
public class WakelockPowerCalculator extends PowerCalculator {
private static final String TAG = "WakelockPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
- private final double mPowerWakelock;
- private long mTotalAppWakelockTimeMs = 0;
+ private final UsageBasedPowerEstimator mPowerEstimator;
+
+ private static class PowerAndDuration {
+ public long durationMs;
+ public double powerMah;
+ }
public WakelockPowerCalculator(PowerProfile profile) {
- mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ profile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
+ }
+
+ @Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final PowerAndDuration result = new PowerAndDuration();
+ UidBatteryConsumer.Builder osBatteryConsumer = null;
+ double osPowerMah = 0;
+ long osDurationMs = 0;
+ long totalAppDurationMs = 0;
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ calculateApp(result, app.getBatteryStatsUid(), rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK, result.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+ totalAppDurationMs += result.durationMs;
+
+ if (app.getUid() == Process.ROOT_UID) {
+ osBatteryConsumer = app;
+ osDurationMs = result.durationMs;
+ osPowerMah = result.powerMah;
+ }
+ }
+
+ // The device has probably been awake for longer than the screen on
+ // time and application wake lock time would account for. Assign
+ // this remainder to the OS, if possible.
+ if (osBatteryConsumer != null) {
+ calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
+ BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
+ osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK,
+ result.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+ }
}
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+ final PowerAndDuration result = new PowerAndDuration();
+ BatterySipper osSipper = null;
+ double osPowerMah = 0;
+ long osDurationMs = 0;
+ long totalAppDurationMs = 0;
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ calculateApp(result, app.uidObj, rawRealtimeUs, statsType);
+ app.wakeLockTimeMs = result.durationMs;
+ app.wakeLockPowerMah = result.powerMah;
+ totalAppDurationMs += result.durationMs;
+
+ if (app.getUid() == Process.ROOT_UID) {
+ osSipper = app;
+ osPowerMah = result.powerMah;
+ osDurationMs = result.durationMs;
+ }
+ }
+ }
// The device has probably been awake for longer than the screen on
// time and application wake lock time would account for. Assign
// this remainder to the OS, if possible.
- BatterySipper osSipper = null;
- for (int i = sippers.size() - 1; i >= 0; i--) {
- BatterySipper app = sippers.get(i);
- if (app.getUid() == 0) {
- osSipper = app;
- break;
- }
- }
-
if (osSipper != null) {
- calculateRemaining(osSipper, batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
+ calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType,
+ osPowerMah, osDurationMs, totalAppDurationMs);
+ osSipper.wakeLockTimeMs = result.durationMs;
+ osSipper.wakeLockPowerMah = result.powerMah;
osSipper.sumPower();
}
}
- @Override
- protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
+ private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs,
+ int statsType) {
long wakeLockTimeUs = 0;
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
u.getWakelockStats();
@@ -73,34 +132,29 @@
wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
}
}
- app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
- mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+ result.durationMs = wakeLockTimeUs / 1000; // convert to millis
// Add cost of holding a wake lock.
- app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000 * 60 * 60);
- if (DEBUG && app.wakeLockPowerMah != 0) {
- Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
- + " power=" + formatCharge(app.wakeLockPowerMah));
+ result.powerMah = mPowerEstimator.calculatePower(result.durationMs);
+ if (DEBUG && result.powerMah != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs
+ + " power=" + formatCharge(result.powerMah));
}
}
- private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
- long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
- wakeTimeMillis -= mTotalAppWakelockTimeMs
- + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+ private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs,
+ long totalAppDurationMs) {
+ final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000
+ - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000
+ - totalAppDurationMs;
if (wakeTimeMillis > 0) {
- final double power = (wakeTimeMillis * mPowerWakelock) / (1000 * 60 * 60);
+ final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
if (DEBUG) {
Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power));
}
- app.wakeLockTimeMs += wakeTimeMillis;
- app.wakeLockPowerMah += power;
+ result.durationMs = osDurationMs + wakeTimeMillis;
+ result.powerMah = osPowerMah + power;
}
}
-
- @Override
- public void reset() {
- mTotalAppWakelockTimeMs = 0;
- }
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adebde0..9840013 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -110,6 +110,7 @@
import android.widget.PopupWindow;
import com.android.internal.R;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.policy.PhoneWindow.PanelFeatureState;
import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback;
import com.android.internal.view.FloatingActionMode;
@@ -255,6 +256,7 @@
private Drawable mOriginalBackgroundDrawable;
private Drawable mLastOriginalBackgroundDrawable;
private Drawable mResizingBackgroundDrawable;
+ private BackgroundBlurDrawable mBackgroundBlurDrawable;
/**
* Temporary holder for a window background when it is set before {@link #mWindow} is
@@ -280,9 +282,14 @@
private final Paint mLegacyNavigationBarBackgroundPaint = new Paint();
private Insets mBackgroundInsets = Insets.NONE;
private Insets mLastBackgroundInsets = Insets.NONE;
+ private int mLastBackgroundBlurRadius = 0;
private boolean mDrawLegacyNavigationBarBackground;
private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
+ private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> {
+ updateBackgroundBlur();
+ return true;
+ };
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
@@ -1263,18 +1270,27 @@
if (mBackgroundInsets == null) {
mBackgroundInsets = Insets.NONE;
}
+
if (mBackgroundInsets.equals(mLastBackgroundInsets)
+ && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius
&& mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
return;
}
- if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) {
- // Call super since we are intercepting setBackground on this class.
- super.setBackgroundDrawable(mOriginalBackgroundDrawable);
- } else {
+ Drawable destDrawable = mOriginalBackgroundDrawable;
+ if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null
+ && mWindow.isTranslucent()) {
+ if (mBackgroundBlurDrawable == null) {
+ mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
+ }
+ destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable,
+ mOriginalBackgroundDrawable});
+ mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius;
+ }
- // Call super since we are intercepting setBackground on this class.
- super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable,
+
+ if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
+ destDrawable = new InsetDrawable(destDrawable,
mBackgroundInsets.left, mBackgroundInsets.top,
mBackgroundInsets.right, mBackgroundInsets.bottom) {
@@ -1286,12 +1302,32 @@
public boolean getPadding(Rect padding) {
return getDrawable().getPadding(padding);
}
- });
+ };
}
+
+ // Call super since we are intercepting setBackground on this class.
+ super.setBackgroundDrawable(destDrawable);
+
mLastBackgroundInsets = mBackgroundInsets;
mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
}
+ private void updateBackgroundBlur() {
+ if (mBackgroundBlurDrawable == null) return;
+
+ // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't
+ // need to calculate the corner radius.
+ if (mWindow.mBackgroundBlurRadius > 0) {
+ if (mOriginalBackgroundDrawable != null) {
+ final Outline outline = new Outline();
+ mOriginalBackgroundDrawable.getOutline(outline);
+ mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT
+ ? outline.getRadius() : 0);
+ }
+ }
+ mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius);
+ }
+
@Override
public Drawable getBackground() {
return mOriginalBackgroundDrawable;
@@ -1722,6 +1758,9 @@
cb.onAttachedToWindow();
}
+ getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+ updateBackgroundDrawable();
+
if (mFeatureId == -1) {
/*
* The main window has been attached, try to restore any panels
@@ -1755,6 +1794,8 @@
cb.onDetachedFromWindow();
}
+ getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+
if (mWindow.mDecorContentParent != null) {
mWindow.mDecorContentParent.dismissPopups();
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 5df175e..d06413c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -258,6 +258,8 @@
Drawable mBackgroundDrawable = null;
Drawable mBackgroundFallbackDrawable = null;
+ int mBackgroundBlurRadius = 0;
+
private boolean mLoadElevation = true;
private float mElevation;
@@ -1523,6 +1525,15 @@
}
@Override
+ public final void setBackgroundBlurRadius(int blurRadius) {
+ super.setBackgroundBlurRadius(blurRadius);
+ if (getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_CROSS_LAYER_BLUR)) {
+ mBackgroundBlurRadius = Math.max(blurRadius, 0);
+ }
+ }
+
+ @Override
public final void setFeatureDrawableResource(int featureId, int resId) {
if (resId != 0) {
DrawableFeatureState st = getDrawableState(featureId, true);
@@ -2549,6 +2560,9 @@
android.R.styleable.Window_windowBlurBehindRadius, 0);
}
+ setBackgroundBlurRadius(a.getDimensionPixelSize(
+ R.styleable.Window_windowBackgroundBlurRadius, 0));
+
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
- int SUBSYSTEM_UNKNOWN = -1;
- int SUBSYSTEM_DISPLAY = 0;
- int NUMBER_SUBSYSTEMS = 1;
- String[] SUBSYSTEM_NAMES = {"display"};
-
-
- @IntDef(prefix = { "SUBSYSTEM_" }, value = {
- SUBSYSTEM_UNKNOWN,
- SUBSYSTEM_DISPLAY,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface MeasuredEnergySubsystem {}
-
- /**
- * Get the subsystem at an index in array.
- *
- * @param index into the array.
- * @return subsystem.
- */
- @MeasuredEnergySubsystem
- int getSubsystem(int index);
-
- /**
- * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
- *
- * @param index into the array.
- * @return energy (in microjoules) consumed since boot.
- */
- long getEnergy(int index);
-
- /**
- * Return number of subsystems in the array.
- */
- int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..d7b4d78 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -239,6 +238,7 @@
* Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
* Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
*/
+ @VisibleForTesting
public long getAccumulatedCustomBucketEnergy(int customBucket) {
if (!isValidCustomBucket(customBucket)) {
return ENERGY_DATA_UNAVAILABLE;
@@ -247,7 +247,18 @@
}
/**
- * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+ * Return accumulated energies (in microjoules) for all custom energy buckets since last reset.
+ */
+ public @NonNull long[] getAccumulatedCustomBucketEnergies() {
+ final long[] energies = new long[getNumberCustomEnergyBuckets()];
+ for (int bucket = 0; bucket < energies.length; bucket++) {
+ energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)];
+ }
+ return energies;
+ }
+
+ /**
+ * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
*/
public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
if (Display.isOnState(screenState)) {
@@ -405,7 +416,6 @@
/** Dump debug data. */
public void dump(PrintWriter pw) {
- pw.println("Accumulated energy since last reset (microjoules):");
pw.print(" ");
for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
pw.print(getBucketName(index));
@@ -432,6 +442,11 @@
return "CUSTOM_" + indexToCustomBucket(index);
}
+ /** Get the number of custom energy buckets on this device. */
+ public int getNumberCustomEnergyBuckets() {
+ return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS;
+ }
+
private static int customBucketToIndex(int customBucket) {
return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
}
@@ -450,7 +465,9 @@
return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
}
- private boolean isValidCustomBucket(int customBucket) {
+ /** Returns whether the given custom bucket is valid (exists) on this device. */
+ @VisibleForTesting
+ public boolean isValidCustomBucket(int customBucket) {
return customBucket >= 0
&& customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 488b18d..c6a040c 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -47,6 +47,13 @@
private CollectionUtils() { /* cannot be instantiated */ }
/**
+ * @see Collection#contains(Object)
+ */
+ public static <T> boolean contains(@Nullable Collection<T> collection, T element) {
+ return collection != null && collection.contains(element);
+ }
+
+ /**
* Returns a list of items from the provided list that match the given condition.
*
* This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f42f468..dc6880e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -219,7 +219,7 @@
}
private static String getTraceTriggerNameForAction(@Action int action) {
- return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
+ return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
}
public static boolean isEnabled(Context ctx) {
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
index 9c87c69..c758504 100644
--- a/core/java/com/android/internal/util/PerfettoTrigger.java
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
@@ -27,16 +28,28 @@
public class PerfettoTrigger {
private static final String TAG = "PerfettoTrigger";
private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+ private static final long THROTTLE_MILLIS = 60000;
+ private static volatile long sLastTriggerTime = -THROTTLE_MILLIS;
/**
* @param triggerName The name of the trigger. Must match the value defined in the AOT
* Perfetto config.
*/
public static void trigger(String triggerName) {
+ // Trace triggering has a non-negligible cost (fork+exec).
+ // To mitigate potential excessive triggering by the API client we ignore calls that happen
+ // too quickl after the most recent trigger.
+ long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime;
+ if (sinceLastTrigger < THROTTLE_MILLIS) {
+ Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger");
+ return;
+ }
+
try {
ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
- Process process = pb.start();
+ pb.start();
+ sLastTriggerTime = SystemClock.elapsedRealtime();
} catch (IOException e) {
Log.w(TAG, "Failed to trigger " + triggerName, e);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index cb586d6..8982519 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -1236,6 +1236,8 @@
if (IncrementalManager.isFeatureEnabled()) {
addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
+ addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION,
+ IncrementalManager.isV2Available() ? 2 : 1);
}
if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8edc8a1..6983d35 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",
@@ -207,6 +208,7 @@
],
shared_libs: [
+ "android.hardware.memtrack-unstable-ndk_platform",
"audioclient-types-aidl-cpp",
"audioflinger-aidl-cpp",
"av-types-aidl-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 c64174b..8dcb210 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,6 +33,7 @@
#include <string>
#include <vector>
+#include <aidl/android/hardware/memtrack/DeviceInfo.h>
#include <android-base/logging.h>
#include <bionic/malloc.h>
#include <debuggerd/client.h>
@@ -45,6 +46,7 @@
#include "jni.h"
#include <dmabufinfo/dmabuf_sysfs_stats.h>
#include <dmabufinfo/dmabufinfo.h>
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
@@ -520,14 +522,15 @@
}
if (outUssSwapPssRss != NULL) {
- if (env->GetArrayLength(outUssSwapPssRss) >= 1) {
+ int outLen = env->GetArrayLength(outUssSwapPssRss);
+ if (outLen >= 1) {
jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0);
if (outUssSwapPssRssArray != NULL) {
outUssSwapPssRssArray[0] = uss;
- if (env->GetArrayLength(outUssSwapPssRss) >= 2) {
+ if (outLen >= 2) {
outUssSwapPssRssArray[1] = swapPss;
}
- if (env->GetArrayLength(outUssSwapPssRss) >= 3) {
+ if (outLen >= 3) {
outUssSwapPssRssArray[2] = rss;
}
}
@@ -536,10 +539,20 @@
}
if (outMemtrack != NULL) {
- if (env->GetArrayLength(outMemtrack) >= 1) {
+ int outLen = env->GetArrayLength(outMemtrack);
+ if (outLen >= 1) {
jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
if (outMemtrackArray != NULL) {
outMemtrackArray[0] = memtrack;
+ if (outLen >= 2) {
+ outMemtrackArray[1] = graphics_mem.graphics;
+ }
+ if (outLen >= 3) {
+ outMemtrackArray[2] = graphics_mem.gl;
+ }
+ if (outLen >= 4) {
+ outMemtrackArray[3] = graphics_mem.other;
+ }
}
env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
}
@@ -816,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;
@@ -838,6 +861,31 @@
return poolsSizeKb;
}
+static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) {
+ std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info;
+ if (!memtrack_gpu_device_info(&gpu_device_info)) {
+ return -1;
+ }
+
+ dmabufinfo::DmabufSysfsStats stats;
+ if (!GetDmabufSysfsStats(&stats)) {
+ return -1;
+ }
+
+ jlong sizeKb = 0;
+ const auto& importer_stats = stats.importer_info();
+ for (const auto& dev_info : gpu_device_info) {
+ const auto& importer_info = importer_stats.find(dev_info.name);
+ if (importer_info == importer_stats.end()) {
+ continue;
+ }
+
+ sizeKb += importer_info->second.size;
+ }
+
+ return sizeKb;
+}
+
static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
jlong dmabufPss = 0;
std::vector<dmabufinfo::DmaBuffer> dmabufs;
@@ -946,6 +994,10 @@
(void*)android_os_Debug_getIonHeapsSizeKb },
{ "getDmabufTotalExportedKb", "()J",
(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/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index 44bff01..2384efa 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -30,6 +30,10 @@
return IncFs_IsEnabled();
}
+static jboolean nativeIsV2Available(JNIEnv* env, jobject clazz) {
+ return !!(IncFs_Features() & INCFS_FEATURE_V2);
+}
+
static jboolean nativeIsIncrementalPath(JNIEnv* env,
jobject clazz,
jstring javaPath) {
@@ -53,12 +57,12 @@
return result;
}
-static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
- {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
- (void*)nativeIsIncrementalPath},
- {"nativeUnsafeGetFileSignature",
- "(Ljava/lang/String;)[B",
- (void*)nativeUnsafeGetFileSignature}};
+static const JNINativeMethod method_table[] =
+ {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+ {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available},
+ {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
+ {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B",
+ (void*)nativeUnsafeGetFileSignature}};
int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index c08363b..dcfa950 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -377,6 +377,10 @@
return (int) sp;
}
+jint android_os_Process_createProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) {
+ return createProcessGroup(uid, pid);
+}
+
/** Sample CPUset list format:
* 0-3,4,6-8
*/
@@ -1358,6 +1362,7 @@
{"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
{"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
{"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
+ {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup},
{"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores},
{"setSwappiness", "(IZ)Z", (void*)android_os_Process_setSwappiness},
{"setArgV0", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 191f748..0aac07d 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -51,19 +51,12 @@
client->decStrong((void*)nativeCreate);
}
-static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
- SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
- client->dispose();
-}
-
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "nativeCreate", "()J",
(void*)nativeCreate },
{ "nativeDestroy", "(J)V",
(void*)nativeDestroy },
- { "nativeKill", "(J)V",
- (void*)nativeKill }
};
int register_android_view_SurfaceSession(JNIEnv* env) {
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/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
new file mode 100644
index 0000000..aab054f
--- /dev/null
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.server.vibrator;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message OneShotProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 duration = 1;
+ repeated int32 amplitude = 2;
+}
+
+message WaveformProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 timings = 1;
+ repeated int32 amplitudes = 2;
+ required bool repeat = 3;
+}
+
+message PrebakedProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 effect_id = 1;
+ optional int32 effect_strength = 2;
+ optional int32 fallback = 3;
+}
+
+message ComposedProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 effect_ids = 1;
+ repeated float effect_scales = 2;
+ repeated int32 delays = 3;
+}
+
+// A com.android.os.VibrationEffect object.
+message VibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional OneShotProto oneshot = 1;
+ optional WaveformProto waveform = 2;
+ optional PrebakedProto prebaked = 3;
+ optional ComposedProto composed = 4;
+}
+
+message SyncVibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated VibrationEffectProto effects = 1;
+ repeated int32 vibrator_ids = 2;
+}
+
+// A com.android.os.CombinedVibrationEffect object.
+message CombinedVibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated SyncVibrationEffectProto effects = 1;
+ repeated int32 delays = 2;
+}
+
+message VibrationAttributesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 usage = 1;
+ optional int32 audio_usage = 2;
+ optional int32 flags = 3;
+}
+
+// Next id: 7
+message VibrationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int64 start_time = 1;
+ optional int64 end_time = 2;
+ optional CombinedVibrationEffectProto effect = 3;
+ optional CombinedVibrationEffectProto original_effect = 4;
+ optional VibrationAttributesProto attributes = 5;
+ optional int32 status = 6;
+}
+
+// Next id: 18
+message VibratorManagerServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 vibrator_ids = 1;
+ optional VibrationProto current_vibration = 2;
+ optional bool is_vibrating = 3;
+ optional VibrationProto current_external_vibration = 4;
+ optional bool vibrator_under_external_control = 5;
+ optional bool low_power_mode = 6;
+ optional int32 haptic_feedback_intensity = 7;
+ optional int32 haptic_feedback_default_intensity = 8;
+ optional int32 notification_intensity = 9;
+ optional int32 notification_default_intensity = 10;
+ optional int32 ring_intensity = 11;
+ optional int32 ring_default_intensity = 12;
+ repeated VibrationProto previous_ring_vibrations = 13;
+ repeated VibrationProto previous_notification_vibrations = 14;
+ repeated VibrationProto previous_alarm_vibrations = 15;
+ repeated VibrationProto previous_vibrations = 16;
+ repeated VibrationProto previous_external_vibrations = 17;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
deleted file mode 100644
index 9e42e9e..0000000
--- a/core/proto/android/server/vibratorservice.proto
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-option java_multiple_files = true;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-
-message OneShotProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 duration = 1;
- repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 timings = 1;
- repeated int32 amplitudes = 2;
- required bool repeat = 3;
-}
-
-message PrebakedProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int32 effect_id = 1;
- optional int32 effect_strength = 2;
- optional int32 fallback = 3;
-}
-
-message ComposedProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 effect_ids = 1;
- repeated float effect_scales = 2;
- repeated int32 delays = 3;
-}
-
-// A com.android.os.VibrationEffect object.
-message VibrationEffectProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional OneShotProto oneshot = 3;
- optional WaveformProto waveform = 1;
- optional PrebakedProto prebaked = 2;
- optional ComposedProto composed = 4;
-}
-
-message VibrationAttributesProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int32 usage = 1;
- optional int32 audio_usage = 2;
- optional int32 flags = 3;
-}
-
-// Next id: 7
-message VibrationProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int64 start_time = 1;
- optional int64 end_time = 4;
- optional VibrationEffectProto effect = 2;
- optional VibrationEffectProto original_effect = 3;
- optional VibrationAttributesProto attributes = 5;
- optional int32 status = 6;
-}
-
-// Next id: 17
-message VibratorServiceDumpProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional VibrationProto current_vibration = 1;
- optional bool is_vibrating = 2;
- optional VibrationProto current_external_vibration = 3;
- optional bool vibrator_under_external_control = 4;
- optional bool low_power_mode = 5;
- optional int32 haptic_feedback_intensity = 6;
- optional int32 haptic_feedback_default_intensity = 14;
- optional int32 notification_intensity = 7;
- optional int32 notification_default_intensity = 15;
- optional int32 ring_intensity = 8;
- optional int32 ring_default_intensity = 16;
- repeated VibrationProto previous_ring_vibrations = 9;
- repeated VibrationProto previous_notification_vibrations = 10;
- repeated VibrationProto previous_alarm_vibrations = 11;
- repeated VibrationProto previous_vibrations = 12;
- repeated VibrationProto previous_external_vibrations = 13;
-}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dc1cc32..fbc1d4f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
android:description="@string/permdesc_accessImsCallService"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+ Only granted if the application is a system app AND is in the Default SMS Role.
+ The permission is revoked when the app is taken out of the Default SMS Role.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to read the user's call log.
<p class="note"><strong>Note:</strong> If your app uses the
{@link #READ_CONTACTS} permission and <em>both</em> your <a
@@ -1862,6 +1870,12 @@
<permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
+ preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
+ <p>Not for use by third-party or privileged applications. -->
+ <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
+ android:protectionLevel="signature" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
@@ -4358,6 +4372,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"
@@ -5457,6 +5477,16 @@
<permission android:name="android.permission.MANAGE_GAME_MODE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+ when they are performing reboot-blocking work.
+ @hide -->
+ <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"/>
<!-- Attribution for Country Detector. -->
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/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
new file mode 100644
index 0000000..513da5e
--- /dev/null
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<android.window.SplashScreenView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical">
+
+ <View android:id="@+id/splashscreen_icon_view"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"/>
+
+ <View android:id="@+id/splashscreen_branding_view"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="60dp"/>
+
+</android.window.SplashScreenView>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index aeb4fc468..67b810e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -362,6 +362,12 @@
surface when the app has not drawn any content into this area. One example is
when the user is resizing a window of an activity in multi-window mode. -->
<attr name="windowBackgroundFallback" format="reference|color" />
+ <!-- Blur the screen behind the window with the bounds of the window.
+ The radius defines the size of the neighbouring area, from which pixels will be
+ averaged to form the final color for each pixel in the region.
+ A radius of 0 means no blur. The higher the radius, the denser the blur.
+ Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. -->
+ <attr name="windowBackgroundBlurRadius" format="dimension" />
<!-- Drawable to use as a frame around the window. -->
<attr name="windowFrame" format="reference" />
<!-- Flag indicating whether there should be no title on this window. -->
@@ -1966,6 +1972,7 @@
<declare-styleable name="Window">
<attr name="windowBackground" />
<attr name="windowBackgroundFallback" />
+ <attr name="windowBackgroundBlurRadius" />
<attr name="windowContentOverlay" />
<attr name="windowFrame" />
<attr name="windowNoTitle" />
@@ -2255,6 +2262,23 @@
-->
<enum name="always" value="3" />
</attr>
+
+ <!-- The background color for the splash screen, if not specify then system will
+ calculate from windowBackground. -->
+ <attr name="windowSplashScreenBackground" format="color"/>
+
+ <!-- Replace an icon in the center of the starting window, if the object is animated
+ and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also
+ play the animation while showing the starting window. -->
+ <attr name="windowSplashScreenAnimatedIcon" format="reference"/>
+ <!-- The duration, in milliseconds, of the window splash screen icon animation duration
+ when playing the splash screen starting window. The maximum animation duration should
+ be limited below 1000ms. -->
+ <attr name="windowSplashScreenAnimationDuration" format="integer"/>
+
+ <!-- Place an drawable image in the bottom of the starting window, it can be used to
+ represent the branding of the application. -->
+ <attr name="windowSplashScreenBrandingImage" format="reference"/>
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -6365,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">
@@ -9253,6 +9284,7 @@
<attr name="shortcutShortLabel" format="reference" />
<attr name="shortcutLongLabel" format="reference" />
<attr name="shortcutDisabledMessage" format="reference" />
+ <attr name="splashScreenTheme" format="reference"/>
</declare-styleable>
<declare-styleable name="ShortcutCategories">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580a..0ae6a76 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
<flag name="recents" value="0x2000000" />
<!-- Additional flag from base permission type: this permission is managed by role. -->
<flag name="role" value="0x4000000" />
+ <!-- Additional flag from base permission type: this permission can also be granted if the
+ requesting application is signed by, or has in its signing lineage, any of the
+ certificate digests declared in {@link android.R.attr#knownCerts}. -->
+ <flag name="knownSigner" value="0x8000000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
@@ -364,6 +368,15 @@
{@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
<attr name="permissionGroup" format="string" />
+ <!-- A reference to an array resource containing the signing certificate digests to be granted
+ this permission when using the {@code knownSigner} protection flag. The digest should
+ be computed over the DER encoding of the trusted certificate using the SHA-256 digest
+ algorithm.
+ <p>
+ If only a single signer is declared this can also be a string resource, or the digest
+ can be declared inline as the value for this attribute. -->
+ <attr name="knownCerts" format="reference|string" />
+
<!-- Specify the name of a user ID that will be shared between multiple
packages. By default, each package gets its own unique user-id.
By setting this value on two or more packages, each of these packages
@@ -1935,6 +1948,7 @@
<attr name="request" />
<attr name="protectionLevel" />
<attr name="permissionFlags" />
+ <attr name="knownCerts" />
</declare-styleable>
<!-- The <code>permission-group</code> tag declares a logical grouping of
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 09ca12a..dd048f3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1590,8 +1590,8 @@
take precedence over lower ones.
See com.android.server.timedetector.TimeDetectorStrategy for available sources. -->
<string-array name="config_autoTimeSourcesPriority">
- <item>telephony</item>
<item>network</item>
+ <item>telephony</item>
</string-array>
<!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
@@ -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 46f928d..fdef08b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3064,6 +3064,14 @@
<public name="previewLayout" />
<public name="clipToOutline" />
<public name="edgeEffectType" />
+ <public name="knownCerts" />
+ <public name="windowBackgroundBlurRadius"/>
+ <public name="windowSplashScreenBackground"/>
+ <public name="windowSplashScreenAnimatedIcon"/>
+ <public name="windowSplashScreenAnimationDuration"/>
+ <public name="windowSplashScreenBrandingImage"/>
+ <public name="splashScreenTheme" />
+ <public name="rippleStyle" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3104,6 +3112,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">
@@ -3119,6 +3132,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/symbols.xml b/core/res/res/values/symbols.xml
index 4109d4c..f66d33b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1818,6 +1818,9 @@
<java-symbol type="string" name="forward_intent_to_owner" />
<java-symbol type="string" name="forward_intent_to_work" />
<java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" />
+ <java-symbol type="layout" name="splash_screen_view" />
+ <java-symbol type="id" name="splashscreen_icon_view" />
+ <java-symbol type="id" name="splashscreen_branding_view" />
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 049ba23..87ae162 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -167,6 +167,9 @@
<!-- Window attributes -->
<item name="windowBackground">@drawable/screen_background_selector_dark</item>
<item name="windowBackgroundFallback">?attr/colorBackground</item>
+ <item name="windowSplashScreenBackground">@color/transparent</item>
+ <item name="windowSplashScreenAnimatedIcon">@null</item>
+ <item name="windowSplashScreenBrandingImage">@null</item>
<item name="windowClipToOutline">false</item>
<item name="windowFrame">@null</item>
<item name="windowNoTitle">false</item>
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/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
similarity index 98%
rename from core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 6bcec25..266ff3d 100644
--- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
new file mode 100644
index 0000000..606e81d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionInfoTest {
+ private static final String KNOWN_CERT_DIGEST_1 =
+ "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+ private static final String KNOWN_CERT_DIGEST_2 =
+ "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6";
+
+ @Test
+ public void createFromParcel_returnsKnownCerts() {
+ // The platform supports a knownSigner permission protection flag that allows one or more
+ // trusted signing certificates to be specified with the permission declaration; if a
+ // requesting app is signed by any of these trusted certificates the permission is granted.
+ // This test verifies the Set of knownCerts is properly parceled / unparceled.
+ PermissionInfo permissionInfo = new PermissionInfo();
+ permissionInfo.protectionLevel =
+ PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+ permissionInfo.knownCerts = new ArraySet<>(2);
+ permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1);
+ permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2);
+ Parcel parcel = Parcel.obtain();
+ permissionInfo.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel);
+
+ assertNotNull(unparceledPermissionInfo.knownCerts);
+ assertEquals(2, unparceledPermissionInfo.knownCerts.size());
+ assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1));
+ assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2));
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index bffd1e4..49b720c 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -29,6 +29,7 @@
import android.content.pm.PackageParser.SigningDetails;
import android.util.ArraySet;
+import android.util.PackageUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -817,6 +818,124 @@
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
}
+ @Test
+ public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
+ // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
+ // is currently signed, or has previously been signed, by any of the certificate digests
+ // in the provided Set. This test verifies if a null Set is provided then false is returned.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(null));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
+ // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
+ // instance of the SigningDetails then false is returned.
+ SigningDetails details = SigningDetails.UNKNOWN;
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
+ // If the single signer of an app is in the provided digest Set then
+ // the method should return true.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
+ // If the single signer of an app is not in the provided digest Set then
+ // the method should return false.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
+ // If an app is signed by multiple signers and all of the signers are in
+ // the digest Set then the method should return true.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
+ throws Exception {
+ // If an app is signed by multiple signers then all signers must be in the digest Set; if
+ // only a subset of the signers are in the Set then the method should return false.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
+ throws Exception {
+ // If an app is signed by multiple signers and the Set size is smaller than the number of
+ // signers then the method should immediately return false since there's no way for the
+ // requirement of all signers in the Set to be met.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
+ // If an app has a rotated signing key and a previous key in the lineage is in the digest
+ // Set then this method should return true.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
+ // If an app has a rotated signing key, but neither the current key nor any of the signers
+ // in the lineage are in the digest set then the method should return false.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
+ throws Exception {
+ // If an app has multiple signers in the lineage only one of those signers must be in the
+ // Set for this method to return true. This test verifies if the last signer in the lineage
+ // is in the set then the method returns true.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
+ throws Exception {
+ // Under some instances an app with only a single signer can have a null lineage; this
+ // test verifies that null lineage does not result in a NullPointerException and instead the
+ // method returns false if the single signer is not in the Set.
+ SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
int[] capabilities = new int[signers.length];
for (int i = 0; i < capabilities.length; i++) {
@@ -853,12 +972,21 @@
// If there are multiple signers then the pastSigningCertificates should be set to null, but
// if there is only a single signer both the current signer and the past signers should be
// set to that one signer.
- if (signers.length > 1) {
+ if (signers.length > 1 || useNullPastSigners) {
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
}
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
}
+ private Set<String> createDigestSet(String... signers) {
+ Set<String> digests = new ArraySet<>();
+ for (String signer : signers) {
+ String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
+ digests.add(digest);
+ }
+ return digests;
+ }
+
private void assertSigningDetailsContainsLineage(SigningDetails details,
String... pastSigners) {
// This method should only be invoked for results that contain a single signer.
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 564103e..11239db 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -17,6 +17,8 @@
package android.os;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
@@ -31,7 +33,6 @@
@Presubmit
@RunWith(JUnit4.class)
public class CombinedVibrationEffectTest {
-
private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
@@ -172,6 +173,37 @@
}
@Test
+ public void testHasVibratorMono_returnsTrueForAnyVibrator() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ assertTrue(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ }
+
+ @Test
+ public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine();
+ assertFalse(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ assertFalse(effect.hasVibrator(2));
+ }
+
+ @Test
+ public void testHasVibratorSequential_returnsNestedVibrators() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(CombinedVibrationEffect.startSynced()
+ .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+ .combine())
+ .combine();
+ assertFalse(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ assertTrue(effect.hasVibrator(2));
+ }
+
+ @Test
public void testSerializationMono() {
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 1a28b73..9a9b474 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,8 +2,11 @@
per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
# Haptics
+per-file CombinedVibrationEffectTest.java = michaelwr@google.com
per-file ExternalVibrationTest.java = michaelwr@google.com
per-file VibrationEffectTest.java = michaelwr@google.com
+per-file VibratorInfoTest.java = michaelwr@google.com
+per-file VibratorTest.java = michaelwr@google.com
# Power
per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
index a43b238..a121941 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
@@ -16,14 +16,14 @@
package android.service.notification;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static com.google.common.truth.Truth.assertThat;
-import android.app.NotificationChannel;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.util.ArraySet;
@@ -45,16 +45,19 @@
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS
| FLAG_FILTER_TYPE_ALERTING
- | FLAG_FILTER_TYPE_SILENT);
+ | FLAG_FILTER_TYPE_SILENT
+ | FLAG_FILTER_TYPE_ONGOING);
assertThat(nlf.getDisallowedPackages()).isEmpty();
- assertThat(nlf.isPackageAllowed("pkg1")).isTrue();
+ assertThat(nlf.isPackageAllowed(new VersionedPackage("any", 0))).isTrue();
}
@Test
public void testConstructor() {
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
@@ -62,20 +65,21 @@
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
- assertThat(nlf.getDisallowedPackages()).contains("pkg1");
- assertThat(nlf.getDisallowedPackages()).contains("pkg2");
- assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
- assertThat(nlf.isPackageAllowed("pkg2")).isFalse();
+ assertThat(nlf.getDisallowedPackages()).contains(a1);
+ assertThat(nlf.getDisallowedPackages()).contains(a2);
+ assertThat(nlf.isPackageAllowed(a1)).isFalse();
+ assertThat(nlf.isPackageAllowed(a2)).isFalse();
}
@Test
public void testSetDisallowedPackages() {
NotificationListenerFilter nlf = new NotificationListenerFilter();
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"});
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(
+ new VersionedPackage[] {new VersionedPackage("pkg1", 0)});
nlf.setDisallowedPackages(pkgs);
- assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
+ assertThat(nlf.isPackageAllowed(new VersionedPackage("pkg1", 0))).isFalse();
}
@Test
@@ -94,7 +98,9 @@
@Test
public void testDescribeContents() {
final int expected = 0;
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.describeContents()).isEqualTo(expected);
@@ -102,7 +108,9 @@
@Test
public void testParceling() {
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
@@ -116,9 +124,9 @@
assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
- assertThat(nlf1.getDisallowedPackages()).contains("pkg1");
- assertThat(nlf1.getDisallowedPackages()).contains("pkg2");
- assertThat(nlf1.isPackageAllowed("pkg1")).isFalse();
- assertThat(nlf1.isPackageAllowed("pkg2")).isFalse();
+ assertThat(nlf1.getDisallowedPackages()).contains(a1);
+ assertThat(nlf1.getDisallowedPackages()).contains(a2);
+ assertThat(nlf1.isPackageAllowed(a1)).isFalse();
+ assertThat(nlf1.isPackageAllowed(a2)).isFalse();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 97c07ea..6652c64 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
import android.os.WorkSource;
+import android.util.SparseLongArray;
import android.view.Display;
import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
}
+ @SmallTest
+ public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setOnBatteryInternal(true);
+
+ final int uid1 = 11500;
+ final int uid2 = 11501;
+
+ // Initially, all custom buckets report energy of 0.
+ checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+ }
+
+ @SmallTest
+ public void testUpdateCustomMeasuredEnergyDataLocked() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ final int bucketA = 0; // Custom bucket 0
+ final int bucketB = 1; // Custom bucket 1
+
+ long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+ long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+ final int uid1 = 10500;
+ long blame1A = 0; // Blame for uid1 in bucketA
+ long blame1B = 0; // Blame for uid1 in bucketB
+
+ final int uid2 = 10501;
+ long blame2A = 0; // Blame for uid2 in bucketA
+ long blame2B = 0; // Blame for uid2 in bucketB
+
+ final SparseLongArray newEnergiesA = new SparseLongArray(2);
+ final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+ // ----- Case A: battery off (so blame does not increase)
+ bi.setOnBatteryInternal(false);
+
+ newEnergiesA.put(uid1, 20_000);
+ // Implicit newEnergiesA.put(uid2, 0);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+ newEnergiesB.put(uid1, 60_000);
+ // Implicit newEnergiesB.put(uid2, 0);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+ checkCustomMeasuredEnergy(
+ "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case B: battery on
+ bi.setOnBatteryInternal(true);
+
+ newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+ // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+ totalBlameA += 310_000;
+
+ newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+ newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+ totalBlameB += 790_000;
+
+ checkCustomMeasuredEnergy(
+ "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case C: battery still on
+ newEnergiesA.delete(uid1); blame1A += 0;
+ newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+ totalBlameA += 560_000;
+
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+ totalBlameB += 10_000;
+
+ checkCustomMeasuredEnergy(
+ "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case D: battery still on
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+ totalBlameB += 15_000;
+ checkCustomMeasuredEnergy(
+ "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
@@ -610,4 +700,33 @@
assertEquals("Wrong doze for Case " + caseName, globalDoze,
bi.getScreenDozeEnergy());
}
+
+ private void checkCustomMeasuredEnergy(String caseName,
+ long totalBlameA, long totalBlameB,
+ int uid1, long blame1A, long blame1B,
+ int uid2, long blame2A, long blame2B,
+ MockBatteryStatsImpl bi) {
+
+ final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules();
+ final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules();
+ final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules();
+
+ assertNotNull(actualTotal);
+ assertNotNull(actualUid1);
+ assertNotNull(actualUid2);
+
+ assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+ actualTotal[0]);
+
+ assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+ actualTotal[1]);
+
+ assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]);
+
+ assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]);
+
+ assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]);
+
+ assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 0fac4f7..2e6e0de 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -68,6 +68,7 @@
SystemServicePowerCalculatorTest.class,
UserPowerCalculatorTest.class,
VideoPowerCalculatorTest.class,
+ WakelockPowerCalculatorTest.class,
com.android.internal.power.MeasuredEnergyStatsTest.class
})
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 0ddc4c0..5edd58f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -49,6 +49,7 @@
};
private BatteryUsageStats mBatteryUsageStats;
+ private boolean mScreenOn;
public BatteryUsageStatsRule() {
Context context = InstrumentationRegistry.getContext();
@@ -97,6 +98,11 @@
return this;
}
+ public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) {
+ mScreenOn = screenOn;
+ return this;
+ }
+
public void setNetworkStats(NetworkStats networkStats) {
mBatteryStats.setNetworkStats(networkStats);
}
@@ -115,6 +121,7 @@
private void noteOnBattery() {
mBatteryStats.setOnBatteryInternal(true);
mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
+ mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
}
public PowerProfile getPowerProfile() {
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 24741fe..dfbf28b 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -18,9 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
import android.os.Binder;
import android.os.Process;
+import android.os.UidBatteryConsumer;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -39,34 +46,48 @@
@RunWith(AndroidJUnit4.class)
public class SystemServicePowerCalculatorTest {
- private static final double PRECISION = 0.0000001;
+ private static final double PRECISION = 0.000001;
@Rule
- public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+ .setNumCpuClusters(2)
+ .setNumSpeedStepsInCpuCluster(0, 2)
+ .setNumSpeedStepsInCpuCluster(1, 2)
+ .setAveragePowerForCpuCluster(0, 360)
+ .setAveragePowerForCpuCluster(1, 480)
+ .setAveragePowerForCpuCore(0, 0, 300)
+ .setAveragePowerForCpuCore(0, 1, 400)
+ .setAveragePowerForCpuCore(1, 0, 500)
+ .setAveragePowerForCpuCore(1, 1, 600);
+ private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
private MockBatteryStatsImpl mMockBatteryStats;
private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
@Before
public void setUp() throws IOException {
+ mMockUserInfoProvider = mock(BatteryStatsImpl.UserInfoProvider.class);
mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
mMockBatteryStats = mStatsRule.getBatteryStats()
.setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
.setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
- .setUserInfoProvider(new MockUserInfoProvider());
+ .setUserInfoProvider(mMockUserInfoProvider);
}
@Test
- public void testCalculateApp() {
- // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
+ public void testPowerProfileBasedModel() {
+ when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+ // Test Power Profile has two CPU clusters with 2 speeds each, thus 4 freq times total
mMockSystemServerCpuThreadReader.setCpuTimes(
- new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
- new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
+ new long[] {30000, 40000, 50000, 60000},
+ new long[] {20000, 30000, 40000, 50000});
mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
- new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000}
+ new long[] {10000, 20000, 30000, 40000}
);
mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -101,14 +122,17 @@
SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new FakeCpuPowerCalculator(), calculator);
assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1)
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
- .isWithin(PRECISION).of(0.00016269);
+ .isWithin(PRECISION).of(1.888888);
assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2)
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
- .isWithin(PRECISION).of(0.00146426);
+ .isWithin(PRECISION).of(17.0);
+ assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
+ .isWithin(PRECISION).of(-18.888888);
}
private static class MockKernelCpuUidFreqTimeReader extends
@@ -137,7 +161,7 @@
}
private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader {
- private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
+ private final SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
MockSystemServerCpuThreadReader() {
super(null);
@@ -154,16 +178,15 @@
}
}
- private static class MockUserInfoProvider extends BatteryStatsImpl.UserInfoProvider {
- @Nullable
+ private static class FakeCpuPowerCalculator extends PowerCalculator {
@Override
- protected int[] getUserIds() {
- return new int[0];
- }
-
- @Override
- public boolean exists(int userId) {
- return true;
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ if (u.getUid() == Process.SYSTEM_UID) {
+ // SystemServer must be attributed at least as much power as the total
+ // of all system services requested by apps.
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 1000000);
+ }
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
new file mode 100644
index 0000000..4f71b43
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.WorkSource;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_PID = 3145;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ mStatsRule.getUidStats(Process.ROOT_UID);
+
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+ BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000);
+ batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+ BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
+
+ mStatsRule.setTime(10_000_000, 6_000_000);
+
+ WakelockPowerCalculator calculator =
+ new WakelockPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+ .isWithin(PRECISION).of(0.1);
+
+ UidBatteryConsumer osConsumer = mStatsRule.getUidBatteryConsumer(Process.ROOT_UID);
+ assertThat(osConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+ .isEqualTo(5000);
+ assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+ .isWithin(PRECISION).of(0.5);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index b9908f4..5fd5a78 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,59 @@
}
@Test
+ public void testIsValidCustomBucket() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+ assertFalse(stats.isValidCustomBucket(-1));
+ assertTrue(stats.isValidCustomBucket(0));
+ assertTrue(stats.isValidCustomBucket(1));
+ assertTrue(stats.isValidCustomBucket(2));
+ assertFalse(stats.isValidCustomBucket(3));
+ assertFalse(stats.isValidCustomBucket(4));
+
+ final MeasuredEnergyStats boringStats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+ assertFalse(boringStats.isValidCustomBucket(-1));
+ assertFalse(boringStats.isValidCustomBucket(0));
+ assertFalse(boringStats.isValidCustomBucket(1));
+ }
+
+ @Test
+ public void testGetAccumulatedCustomBucketEnergies() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
+ stats.updateCustomBucket(2, 13, true);
+ stats.updateCustomBucket(1, 70, true);
+
+ final long[] output = stats.getAccumulatedCustomBucketEnergies();
+ assertEquals(3, output.length);
+
+ assertEquals(50, output[0]);
+ assertEquals(60 + 70, output[1]);
+ assertEquals(13, output[2]);
+ }
+
+ @Test
+ public void testGetAccumulatedCustomBucketEnergies_empty() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+
+ final long[] output = stats.getAccumulatedCustomBucketEnergies();
+ assertEquals(0, output.length);
+ }
+
+ @Test
+ public void testGetNumberCustomEnergyBuckets() {
+ assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0)
+ .getNumberCustomEnergyBuckets());
+ assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3)
+ .getNumberCustomEnergyBuckets());
+ }
+
+ @Test
public void testReset() {
final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
final int numCustomBuckets = 2;
diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..b40fe24
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS
@@ -0,0 +1,3 @@
+# LockSettings related
+per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 65d3a01..549e074 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -6,7 +6,6 @@
jsharkey@android.com
jsharkey@google.com
lorenzo@google.com
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
toddke@android.com
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index cf0199b..c940574 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -26,6 +26,7 @@
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
+ <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -40,10 +41,12 @@
<permission name="android.permission.MOVE_PACKAGE"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
+ <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
<permission name="android.permission.SET_TIME"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/car/com.android.car.provision.xml b/data/etc/car/com.android.car.provision.xml
index 4fd9cae..42cfd3c 100644
--- a/data/etc/car/com.android.car.provision.xml
+++ b/data/etc/car/com.android.car.provision.xml
@@ -17,6 +17,7 @@
<permissions>
<privapp-permissions package="com.android.car.provision">
<permission name="android.car.permission.CAR_POWERTRAIN"/>
+ <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.MANAGE_USERS"/>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index 19548bc..48f6ab3 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -25,6 +25,7 @@
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.READ_LOGS"/>
+ <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 3a20a9c..bd30d7a 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -27,8 +27,10 @@
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
+ <permission name="android.permission.LOCK_DEVICE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MASTER_CLEAR"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
@@ -40,9 +42,11 @@
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.RESET_PASSWORD"/>
<permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
+ <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 734561c..fa92b6d 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,6 +19,7 @@
<!-- Required to place emergency calls from emergency info screen. -->
<permission name="android.permission.CALL_PRIVILEGED"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml
index d2ea0ec..68f8298 100644
--- a/data/etc/com.android.provision.xml
+++ b/data/etc/com.android.provision.xml
@@ -17,7 +17,7 @@
<permissions>
<privapp-permissions package="com.android.provision">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
- <permissionn ame="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+ <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
<permission name="android.permission.MASTER_CLEAR"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e473c55..3fdb0da 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -58,5 +58,6 @@
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_DREAM_SUPPRESSION"/>
+ <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0484a9a..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"/>
@@ -474,6 +476,8 @@
<!-- Permission required for GTS test - GtsAssistIntentTestCases -->
<permission name="android.permission.MANAGE_SOUND_TRIGGER" />
<permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
+ <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+ <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 222c9bd..ded4a27 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -811,12 +811,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "-1144293044": {
- "message": "SURFACE SET FREEZE LAYER: %s",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
- },
"-1142279614": {
"message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
"level": "VERBOSE",
@@ -895,12 +889,6 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
- "-1088782910": {
- "message": "Translucent=%s Floating=%s ShowWallpaper=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1076978367": {
"message": "thawRotation: mRotation=%d",
"level": "VERBOSE",
@@ -1261,12 +1249,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
- "-639305784": {
- "message": "Could not report config changes to the window token client.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowToken.java"
- },
"-639217716": {
"message": "setFocusedApp %s displayId=%d Callers=%s",
"level": "INFO",
@@ -1417,12 +1399,6 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "-477481651": {
- "message": "SURFACE DESTROY PENDING: %s. %s",
- "level": "INFO",
- "group": "WM_SHOW_SURFACE_ALLOC",
- "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
- },
"-463348344": {
"message": "Removing and adding activity %s to root task at top callers=%s",
"level": "INFO",
@@ -1693,6 +1669,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "-124316973": {
+ "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STARTING_WINDOW",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-118786523": {
"message": "Resume failed; resetting state to %s: %s",
"level": "VERBOSE",
@@ -1903,12 +1885,6 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "123161180": {
- "message": "SEVER CHILDREN",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
- },
"140319294": {
"message": "IME target changed within ActivityRecord",
"level": "DEBUG",
@@ -2569,12 +2545,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "838570988": {
- "message": "Could not report token removal to the window token client.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowToken.java"
- },
"872933199": {
"message": "Changing focus from %s to %s displayId=%d Callers=%s",
"level": "DEBUG",
@@ -3235,6 +3205,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "1699269281": {
+ "message": "Don't organize or trigger events for untrusted displayId=%d",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+ },
"1720229827": {
"message": "Creating animation bounds layer",
"level": "INFO",
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/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..32c777c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1346,6 +1346,19 @@
}
}
+ static {
+ // Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
+ // TODO: add new attribute to fonts.xml to preload fonts in Zygote.
+ preloadFontFile("/system/fonts/Roboto-Regular.ttf");
+ }
+
+ private static void preloadFontFile(String filePath) {
+ File file = new File(filePath);
+ if (file.exists()) {
+ nativeWarmUpCache(filePath);
+ }
+ }
+
/** @hide */
@VisibleForTesting
public static void destroySystemFontMap() {
@@ -1414,6 +1427,16 @@
return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
}
+ /** @hide */
+ public List<FontFamily> getFallback() {
+ ArrayList<FontFamily> families = new ArrayList<>();
+ int familySize = nativeGetFamilySize(native_instance);
+ for (int i = 0; i < familySize; ++i) {
+ families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+ }
+ return families;
+ }
+
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
@@ -1439,6 +1462,13 @@
@CriticalNative
private static native long nativeGetReleaseFunc();
+ @CriticalNative
+ private static native int nativeGetFamilySize(long naitvePtr);
+
+ @CriticalNative
+ private static native long nativeGetFamily(long nativePtr, int index);
+
+
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
private static native int nativeWriteTypefaces(
@@ -1447,4 +1477,6 @@
private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer);
private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
+
+ private static native void nativeWarmUpCache(String fileName);
}
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/Font.java b/graphics/java/android/graphics/fonts/Font.java
index b153c99..f826b24 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -27,7 +27,6 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
-import android.util.LongSparseLongArray;
import android.util.TypedValue;
import com.android.internal.annotations.GuardedBy;
@@ -63,10 +62,9 @@
NativeAllocationRegistry.createMalloced(
ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont());
- private static final Object SOURCE_ID_LOCK = new Object();
- @GuardedBy("SOURCE_ID_LOCK")
- private static final LongSparseLongArray FONT_SOURCE_ID_MAP =
- new LongSparseLongArray(300); // System font has 200 fonts, so 300 should be enough.
+ private static final NativeAllocationRegistry FONT_REGISTRY =
+ NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
+ nGetReleaseNativeFont());
/**
* A builder class for creating new Font.
@@ -519,18 +517,19 @@
private @Nullable FontVariationAxis[] mAxes = null;
@GuardedBy("mLock")
private @NonNull LocaleList mLocaleList = null;
- @GuardedBy("mLock")
- private int mSourceIdentifier = -1;
/**
* Use Builder instead
*
* Caller must increment underlying minikin::Font ref count.
+ * This class takes the ownership of the passing native objects.
*
* @hide
*/
public Font(long nativePtr) {
mNativePtr = nativePtr;
+
+ FONT_REGISTRY.registerNativeAllocation(this, mNativePtr);
}
/**
@@ -751,20 +750,7 @@
* @return an unique identifier for the font source data.
*/
public int getSourceIdentifier() {
- synchronized (mLock) {
- if (mSourceIdentifier == -1) {
- long bufferAddress = nGetBufferAddress(mNativePtr);
- synchronized (SOURCE_ID_LOCK) {
- long id = FONT_SOURCE_ID_MAP.get(bufferAddress, -1);
- if (id == -1) {
- id = FONT_SOURCE_ID_MAP.size();
- FONT_SOURCE_ID_MAP.append(bufferAddress, id);
- }
- mSourceIdentifier = (int) id;
- }
- }
- return mSourceIdentifier;
- }
+ return nGetSourceId(mNativePtr);
}
/**
@@ -883,6 +869,9 @@
private static native long nGetBufferAddress(long font);
@CriticalNative
+ private static native int nGetSourceId(long font);
+
+ @CriticalNative
private static native long nGetReleaseNativeFont();
@FastNative
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
nAddFont(builderPtr, mFonts.get(i).getNativePtr());
}
final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
- final FontFamily family = new FontFamily(mFonts, ptr);
+ final FontFamily family = new FontFamily(ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
}
@@ -146,7 +146,8 @@
private final long mNativePtr;
// Use Builder instead.
- private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+ /** @hide */
+ public FontFamily(long ptr) {
mNativePtr = ptr;
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
*/
public static @NonNull Set<Font> getAvailableFonts() {
synchronized (LOCK) {
- if (sAvailableFonts != null) {
- return sAvailableFonts;
- }
-
- if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
- sAvailableFonts = collectAllFonts();
- } else {
+ if (sAvailableFonts == null) {
Set<Font> set = new ArraySet<>();
- for (FontFamily[] items : sFamilyMap.values()) {
- for (FontFamily family : items) {
- for (int i = 0; i < family.getSize(); ++i) {
- set.add(family.getFont(i));
+ for (Typeface tf : Typeface.getSystemFontMap().values()) {
+ List<FontFamily> families = tf.getFallback();
+ for (int i = 0; i < families.size(); ++i) {
+ FontFamily family = families.get(i);
+ for (int j = 0; j < family.getSize(); ++j) {
+ set.add(family.getFont(j));
}
}
}
-
sAvailableFonts = Collections.unmodifiableSet(set);
}
return sAvailableFonts;
}
}
- private static @NonNull Set<Font> collectAllFonts() {
- // TODO: use updated fonts
- FontConfig fontConfig = getSystemPreinstalledFontConfig();
- Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
- Set<Font> res = new ArraySet<>();
- for (FontFamily[] families : map.values()) {
- for (FontFamily family : families) {
- for (int i = 0; i < family.getSize(); ++i) {
- res.add(family.getFont(i));
- }
- }
- }
- return res;
- }
-
private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
try (FileInputStream file = new FileInputStream(fullPath)) {
final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
-
- /**
- * @hide
- */
- public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
- synchronized (LOCK) {
- sFamilyMap = fallbackMap;
- }
- }
}
diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS
index f04e200..057dc0d 100644
--- a/graphics/java/android/graphics/pdf/OWNERS
+++ b/graphics/java/android/graphics/pdf/OWNERS
@@ -5,4 +5,3 @@
sumir@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-moltmann@google.com
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 334b111..988838b 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -17,6 +17,7 @@
package android.security.keystore;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Build;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
@@ -25,6 +26,8 @@
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -477,11 +480,11 @@
success = true;
return keyPair;
- } catch (ProviderException e) {
+ } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) {
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(e);
} else {
- throw e;
+ throw new ProviderException(e);
}
} finally {
if (!success) {
@@ -491,7 +494,7 @@
}
private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
- throws ProviderException {
+ throws ProviderException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
KeymasterArguments args = new KeymasterArguments();
@@ -510,6 +513,60 @@
Build.MODEL.getBytes(StandardCharsets.UTF_8));
}
+ int[] idTypes = mSpec.getAttestationIds();
+ if (idTypes != null) {
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+ || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+ telephonyService =
+ (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException(
+ "Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case AttestationUtils.ID_TYPE_SERIAL:
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ case AttestationUtils.ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ }
+ case AttestationUtils.ID_TYPE_MEID: {
+ final String meid = telephonyService.getMeid(0);
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ }
+ case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+ args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
+ }
+ }
+
return getAttestationChain(privateKeyAlias, keyPair, args);
}
@@ -547,7 +604,8 @@
}
}
- private KeymasterArguments constructKeyGenerationArguments() {
+ private KeymasterArguments constructKeyGenerationArguments()
+ throws IllegalArgumentException, DeviceIdAttestationException {
KeymasterArguments args = new KeymasterArguments();
args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -565,9 +623,9 @@
mSpec.getKeyValidityForConsumptionEnd());
addAlgorithmSpecificParameters(args);
- if (mSpec.isUniqueIdIncluded())
+ if (mSpec.isUniqueIdIncluded()) {
args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
-
+ }
return args;
}
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index c8c1de4..f22b604 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -34,6 +34,14 @@
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
+ /**
+ * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the
+ * array.
+ */
+ public static int[] cloneIfNotEmpty(int[] array) {
+ return ((array != null) && (array.length > 0)) ? array.clone() : array;
+ }
+
public static byte[] cloneIfNotEmpty(byte[] array) {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c2a7b2e..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 {
@@ -267,6 +308,7 @@
private final boolean mUserPresenceRequired;
private final byte[] mAttestationChallenge;
private final boolean mDevicePropertiesAttestationIncluded;
+ private final int[] mAttestationIds;
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mInvalidatedByBiometricEnrollment;
@@ -308,6 +350,7 @@
boolean userPresenceRequired,
byte[] attestationChallenge,
boolean devicePropertiesAttestationIncluded,
+ int[] attestationIds,
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
@@ -361,6 +404,7 @@
mUserAuthenticationType = userAuthenticationType;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded;
+ mAttestationIds = attestationIds;
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
@@ -720,6 +764,25 @@
}
/**
+ * @hide
+ * Allows the caller to specify device IDs to be attested to in the certificate for the
+ * generated key pair. These values are the enums specified in
+ * {@link android.security.keystore.AttestationUtils}
+ *
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+ * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+ *
+ * @return integer array representing the requested device IDs to attest.
+ */
+ @SystemApi
+ @Nullable
+ public int[] getAttestationIds() {
+ return Utils.cloneIfNotNull(mAttestationIds);
+ }
+
+ /**
* @hide This is a system-only API
*
* Returns {@code true} if the attestation certificate will contain a unique ID field.
@@ -834,6 +897,7 @@
private boolean mUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mDevicePropertiesAttestationIncluded = false;
+ private int[] mAttestationIds = null;
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
@@ -902,6 +966,7 @@
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mDevicePropertiesAttestationIncluded =
sourceSpec.isDevicePropertiesAttestationIncluded();
+ mAttestationIds = sourceSpec.getAttestationIds();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
@@ -1473,6 +1538,26 @@
}
/**
+ * @hide
+ * Sets which IDs to attest in the attestation certificate for the key. The acceptable
+ * values in this integer array are the enums specified in
+ * {@link android.security.keystore.AttestationUtils}
+ *
+ * @param attestationIds the array of ID types to attest to in the certificate.
+ *
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+ * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+ */
+ @SystemApi
+ @NonNull
+ public Builder setAttestationIds(@NonNull int[] attestationIds) {
+ mAttestationIds = attestationIds;
+ return this;
+ }
+
+ /**
* @hide Only system apps can use this method.
*
* Sets whether to include a temporary unique ID field in the attestation certificate.
@@ -1638,6 +1723,7 @@
mUserPresenceRequired,
mAttestationChallenge,
mDevicePropertiesAttestationIncluded,
+ mAttestationIds,
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
mInvalidatedByBiometricEnrollment,
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/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 8163472..1f2f853 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
out.writeBoolean(mSpec.isUserPresenceRequired());
out.writeByteArray(mSpec.getAttestationChallenge());
out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded());
+ out.writeIntArray(mSpec.getAttestationIds());
out.writeBoolean(mSpec.isUniqueIdIncluded());
out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
@@ -160,6 +161,7 @@
final boolean userPresenceRequired = in.readBoolean();
final byte[] attestationChallenge = in.createByteArray();
final boolean devicePropertiesAttestationIncluded = in.readBoolean();
+ final int[] attestationIds = in.createIntArray();
final boolean uniqueIdIncluded = in.readBoolean();
final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
final boolean invalidatedByBiometricEnrollment = in.readBoolean();
@@ -195,6 +197,7 @@
userPresenceRequired,
attestationChallenge,
devicePropertiesAttestationIncluded,
+ attestationIds,
uniqueIdIncluded,
userAuthenticationValidWhileOnBody,
invalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 5722c7b..e58b1cc 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -33,4 +33,8 @@
static byte[] cloneIfNotNull(byte[] value) {
return (value != null) ? value.clone() : null;
}
+
+ static int[] cloneIfNotNull(int[] value) {
+ return (value != null) ? value.clone() : null;
+ }
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 70e30d2..4d27c34 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,16 +18,20 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
+import android.security.keystore.AttestationUtils;
+import android.security.keystore.DeviceIdAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
@@ -38,6 +42,8 @@
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.Log;
import libcore.util.EmptyArray;
@@ -478,7 +484,8 @@
}
throw p;
}
- } catch (UnrecoverableKeyException e) {
+ } catch (UnrecoverableKeyException | IllegalArgumentException
+ | DeviceIdAttestationException e) {
throw new ProviderException(
"Failed to construct key object from newly generated key pair.", e);
} finally {
@@ -496,7 +503,7 @@
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
- throws ProviderException {
+ throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
@@ -526,15 +533,69 @@
Build.MODEL.getBytes(StandardCharsets.UTF_8)
));
}
- } else {
- if (mSpec.isDevicePropertiesAttestationIncluded()) {
- throw new ProviderException("An attestation challenge must be provided when "
- + "requesting device properties attestation.");
+
+ int[] idTypes = mSpec.getAttestationIds();
+ if (idTypes == null) {
+ return;
+ }
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+ || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+ telephonyService =
+ (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException("Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case AttestationUtils.ID_TYPE_SERIAL:
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ case AttestationUtils.ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ }
+ case AttestationUtils.ID_TYPE_MEID: {
+ final String meid = telephonyService.getMeid(0);
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ }
+ case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+ params.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
}
}
}
- private Collection<KeyParameter> constructKeyGenerationArguments() {
+ private Collection<KeyParameter> constructKeyGenerationArguments()
+ throws DeviceIdAttestationException, IllegalArgumentException {
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
params.add(KeyStore2ParameterUtils.makeEnum(
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/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 13f1fdd..2419865 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -48,4 +48,7 @@
<!-- one handed background panel default alpha -->
<item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
+
+ <!-- maximum animation duration for the icon when entering the starting window -->
+ <integer name="max_starting_window_intro_icon_anim_duration">1000</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 034e65c..583964b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -173,4 +173,13 @@
<!-- 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>
+
+ <!-- The height of the brand image on staring surface. -->
+ <dimen name="starting_surface_brand_image_height">80dp</dimen>
</resources>
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/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
package com.android.wm.shell;
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
*/
+@ExternalThread
public interface ShellCommandHandler {
/**
* Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
@@ -38,24 +37,24 @@
public final class ShellCommandHandlerImpl {
private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
- private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
- private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<HideDisplayCutout> mHideDisplayCutout;
+ private final Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+ private final Optional<AppPairsController> mAppPairsOptional;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<AppPairs> mAppPairsOptional;
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
public static ShellCommandHandler create(
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
ShellExecutor mainExecutor) {
return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
private ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -39,9 +38,9 @@
private final DisplayImeController mDisplayImeController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<AppPairs> mAppPairsOptional;
+ private final Optional<AppPairsController> mAppPairsOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
@@ -51,9 +50,9 @@
public static ShellInit create(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
+ Optional<AppPairsController> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
private ShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
+ Optional<AppPairsController> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
// Register the shell organizer
mShellTaskOrganizer.registerOrganizer();
- mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+ mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
// Bind the splitscreen impl to the drag drop controller
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 a570c0a..efc55c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -25,6 +25,8 @@
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
@@ -38,13 +40,10 @@
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -83,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) {};
}
@@ -108,26 +117,26 @@
* compat.
*/
@Nullable
- private final SizeCompatUI mSizeCompatUI;
+ private final SizeCompatUIController mSizeCompatUI;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+ this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+ new StartingSurfaceDrawer(context, mainExecutor));
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- SizeCompatUI sizeCompatUI) {
- this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
+ SizeCompatUIController sizeCompatUI) {
+ this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+ new StartingSurfaceDrawer(context, mainExecutor));
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable SizeCompatUI sizeCompatUI) {
+ Context context, @Nullable SizeCompatUIController sizeCompatUI,
+ StartingSurfaceDrawer startingSurfaceDrawer) {
super(taskOrganizerController, mainExecutor);
- // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
- // by a controller, that class should be create while porting
- // ActivityRecord#addStartingWindow to WMShell.
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
mSizeCompatUI = sizeCompatUI;
+ mStartingSurfaceDrawer = startingSurfaceDrawer;
}
@Override
@@ -254,6 +263,11 @@
}
@Override
+ public void copySplashScreenView(int taskId) {
+ mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
@@ -342,8 +356,8 @@
}
/**
- * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
- * the UI accordingly.
+ * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+ * to update the UI accordingly.
*
* @param taskInfo the new Task info
* @param taskListener listener to handle the Task Surface placement. {@code null} if task is
@@ -354,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 */);
@@ -363,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/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
+ private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
mShellExecutor = shellExecutor;
}
+ public TaskViewFactory asTaskViewFactory() {
+ return mImpl;
+ }
+
/** Creates an {@link TaskView} */
- @ShellMainThread
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
TaskView taskView = new TaskView(context, mTaskOrganizer);
executor.execute(() -> {
@@ -46,10 +50,6 @@
});
}
- public TaskViewFactory getTaskViewFactory() {
- return new TaskViewFactoryImpl();
- }
-
private class TaskViewFactoryImpl implements TaskViewFactory {
@ExternalThread
public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
/**
* The singleton wrapper to communicate between WindowManagerService and WMShell features
* (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
*/
public class WindowManagerShellWrapper {
private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
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/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
/** Unpairs any app-pair containing this task id. */
void unpair(int taskId);
- /** Dumps current status of app pairs. */
- void dump(@NonNull PrintWriter pw, String prefix);
- /** Called when the shell organizer has been registered. */
- void onOrganizerRegistered();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
private final DisplayController mDisplayController;
- /**
- * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
- */
- @Nullable
- public static AppPairs create(ShellTaskOrganizer organizer,
- SyncTransactionQueue syncQueue, DisplayController displayController,
- ShellExecutor mainExecutor) {
- return new AppPairsController(organizer, syncQueue, displayController,
- mainExecutor).mImpl;
- }
-
- AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+ public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
DisplayController displayController, ShellExecutor mainExecutor) {
mTaskOrganizer = organizer;
mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
mMainExecutor = mainExecutor;
}
- void onOrganizerRegistered() {
+ public AppPairs asAppPairs() {
+ return mImpl;
+ }
+
+ public void onOrganizerRegistered() {
if (mPairsPool == null) {
setPairsPool(new AppPairsPool(this));
}
}
@VisibleForTesting
- void setPairsPool(AppPairsPool pool) {
+ public void setPairsPool(AppPairsPool pool) {
mPairsPool = pool;
}
- boolean pair(int taskId1, int taskId2) {
+ public boolean pair(int taskId1, int taskId2) {
final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
return pair(task1, task2);
}
- boolean pair(ActivityManager.RunningTaskInfo task1,
+ public boolean pair(ActivityManager.RunningTaskInfo task1,
ActivityManager.RunningTaskInfo task2) {
return pairInner(task1, task2) != null;
}
@VisibleForTesting
- AppPair pairInner(
+ public AppPair pairInner(
@NonNull ActivityManager.RunningTaskInfo task1,
@NonNull ActivityManager.RunningTaskInfo task2) {
final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
return pair;
}
- void unpair(int taskId) {
+ public void unpair(int taskId) {
unpair(taskId, true /* releaseToPool */);
}
- void unpair(int taskId, boolean releaseToPool) {
+ public void unpair(int taskId, boolean releaseToPool) {
AppPair pair = mActiveAppPairs.get(taskId);
if (pair == null) {
for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
}
}
- ShellTaskOrganizer getTaskOrganizer() {
+ public ShellTaskOrganizer getTaskOrganizer() {
return mTaskOrganizer;
}
- SyncTransactionQueue getSyncTransactionQueue() {
+ public SyncTransactionQueue getSyncTransactionQueue() {
return mSyncQueue;
}
- DisplayController getDisplayController() {
+ public DisplayController getDisplayController() {
return mDisplayController;
}
- private void dump(@NonNull PrintWriter pw, String prefix) {
+ public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
@@ -202,21 +195,5 @@
AppPairsController.this.unpair(taskId);
});
}
-
- @Override
- public void onOrganizerRegistered() {
- mMainExecutor.execute(() -> {
- AppPairsController.this.onOrganizerRegistered();
- });
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- try {
- mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump AppPairsController in 2s");
- }
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d73fc6d..047df5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -86,6 +86,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -192,9 +193,9 @@
private boolean mIsStatusBarShade = true;
/**
- * Injected constructor.
+ * Creates an instance of the BubbleController.
*/
- public static Bubbles create(Context context,
+ public static BubbleController create(Context context,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@Nullable IStatusBarService statusBarService,
@@ -211,14 +212,14 @@
return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
- logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+ logger, organizer, positioner, mainExecutor, mainHandler);
}
/**
* Testing constructor.
*/
@VisibleForTesting
- public BubbleController(Context context,
+ protected BubbleController(Context context,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +323,7 @@
}
@VisibleForTesting
- public Bubbles getImpl() {
+ public Bubbles asBubbles() {
return mImpl;
}
@@ -586,11 +587,15 @@
// There were no bubbles saved for this used.
return;
}
- for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) {
- if (canLaunchInActivityView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
- }
- }
+ mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+ mMainExecutor.execute(() -> {
+ for (BubbleEntry e : entries) {
+ if (canLaunchInActivityView(mContext, e)) {
+ updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+ }
+ }
+ });
+ });
// Finally, remove the entries for this user now that bubbles are restored.
mSavedBubbleKeysPerUser.remove(mCurrentUserId);
}
@@ -856,21 +861,24 @@
}
}
- private void onRankingUpdated(RankingMap rankingMap) {
+ private void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
if (mTmpRanking == null) {
mTmpRanking = new NotificationListenerService.Ranking();
}
String[] orderedKeys = rankingMap.getOrderedKeys();
for (int i = 0; i < orderedKeys.length; i++) {
String key = orderedKeys[i];
- BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key);
+ Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
+ BubbleEntry entry = entryData.first;
+ boolean shouldBubbleUp = entryData.second;
rankingMap.getRanking(key, mTmpRanking);
boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
if (isActiveBubble && !mTmpRanking.canBubble()) {
// If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
// This means that the app or channel's ability to bubble has been revoked.
mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
- } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) {
+ } else if (isActiveBubble && !shouldBubbleUp) {
// If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
// This happens when DND is enabled and configured to hide bubbles. Dismissing with
// the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
@@ -919,17 +927,20 @@
private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
Objects.requireNonNull(b);
b.setIsBubble(isBubble);
- final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey());
- if (entry != null) {
- // Updating the entry to be a bubble will trigger our normal update flow
- setIsBubble(entry, isBubble, b.shouldAutoExpand());
- } else if (isBubble) {
- // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
- // stack ourselves
- Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
- inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
- !bubble.shouldAutoExpand() /* showInShade */);
- }
+ mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
+ mMainExecutor.execute(() -> {
+ if (entry != null) {
+ // Updating the entry to be a bubble will trigger our normal update flow
+ setIsBubble(entry, isBubble, b.shouldAutoExpand());
+ } else if (isBubble) {
+ // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
+ // stack ourselves
+ Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
+ inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
+ !bubble.shouldAutoExpand() /* showInShade */);
+ }
+ });
+ });
}
@SuppressWarnings("FieldCanBeLocal")
@@ -992,14 +1003,17 @@
}
}
- final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
- if (entry != null) {
- final String groupKey = entry.getStatusBarNotification().getGroupKey();
- if (getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
- }
- }
+ mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> {
+ mMainExecutor.execute(() -> {
+ if (entry != null) {
+ final String groupKey = entry.getStatusBarNotification().getGroupKey();
+ if (getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to potentially remove the summary
+ mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
+ }
+ }
+ });
+ });
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
@@ -1121,23 +1135,6 @@
mStackView.updateContentDescription();
}
- /**
- * The task id of the expanded view, if the stack is expanded and not occluded by the
- * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
- */
- private int getExpandedTaskId() {
- if (mStackView == null) {
- return INVALID_TASK_ID;
- }
- final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
- if (expandedViewProvider != null && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mSysuiProxy.isNotificationShadeExpand()) {
- return expandedViewProvider.getTaskId();
- }
- return INVALID_TASK_ID;
- }
-
@VisibleForTesting
public BubbleStackView getStackView() {
return mStackView;
@@ -1343,9 +1340,10 @@
}
@Override
- public void onRankingUpdated(RankingMap rankingMap) {
+ public void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
mMainExecutor.execute(() -> {
- BubbleController.this.onRankingUpdated(rankingMap);
+ BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2100428..2f31acd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -163,6 +163,7 @@
// Apply flags to make behaviour match documentLaunchMode=always.
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true);
if (mBubble != null) {
mBubble.setIntentActive();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 6a1026b..8e061e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -26,6 +26,7 @@
import android.os.Looper;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.IntDef;
@@ -37,6 +38,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -182,8 +184,11 @@
* permissions on the notification channel or the global setting.
*
* @param rankingMap the updated ranking map from NotificationListenerService
+ * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
+ * bubble up
*/
- void onRankingUpdated(RankingMap rankingMap);
+ void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);
/**
* Called when the status bar has become visible or invisible (either permanently or
@@ -243,14 +248,10 @@
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
- @Nullable
- BubbleEntry getPendingOrActiveEntry(String key);
+ void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
- List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys);
-
- boolean isNotificationShadeExpand();
-
- boolean shouldBubbleUp(String key);
+ void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ Consumer<List<BubbleEntry>> callback);
void setNotificationInterruption(String key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
public interface HideDisplayCutout {
/**
* Notifies {@link Configuration} changed.
- * @param newConfig
*/
void onConfigurationChanged(Configuration newConfig);
-
- /**
- * Dumps hide display cutout status.
- */
- void dump(@NonNull PrintWriter pw);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
@VisibleForTesting
boolean mEnabled;
- HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
- ShellExecutor mainExecutor) {
- mContext = context;
- mOrganizer = organizer;
- mMainExecutor = mainExecutor;
- updateStatus();
- }
-
/**
* Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
* supported.
*/
@Nullable
- public static HideDisplayCutout create(
+ public static HideDisplayCutoutController create(
Context context, DisplayController displayController, ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+ return new HideDisplayCutoutController(context, organizer, mainExecutor);
+ }
+
+ HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+ ShellExecutor mainExecutor) {
+ mContext = context;
+ mOrganizer = organizer;
+ mMainExecutor = mainExecutor;
+ updateStatus();
+ }
+
+ public HideDisplayCutout asHideDisplayCutout() {
+ return mImpl;
}
@VisibleForTesting
@@ -94,7 +98,7 @@
updateStatus();
}
- private void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
final String prefix = " ";
pw.print(TAG);
pw.println(" states: ");
@@ -111,14 +115,5 @@
HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
});
}
-
- @Override
- public void dump(@NonNull PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
- }
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
private volatile boolean mAdjustedForIme = false;
private boolean mHomeStackResizable = false;
- /**
- * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
- */
- @Nullable
- public static LegacySplitScreen create(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController imeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
- return new LegacySplitScreenController(context, displayController, systemWindows,
- imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
- transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
- }
-
public LegacySplitScreenController(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
}
});
}
+
+ public LegacySplitScreen asLegacySplitScreen() {
+ return mImpl;
+ }
- void onSplitScreenSupported() {
+ public void onSplitScreenSupported() {
// Set starting tile bounds based on middle target
final WindowContainerTransaction tct = new WindowContainerTransaction();
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
mTaskOrganizer.applyTransaction(tct);
}
- private void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean showing) {
if (!isSplitActive() || mView == null) {
return;
}
@@ -293,19 +282,19 @@
}
}
- boolean isMinimized() {
+ public boolean isMinimized() {
return mMinimized;
}
- boolean isHomeStackResizable() {
+ public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
- DividerView getDividerView() {
+ public DividerView getDividerView() {
return mView;
}
- boolean isDividerVisible() {
+ public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
@@ -314,13 +303,13 @@
* isDividerVisible because the divider is only visible once *everything* is in split mode
* while this only cares if some things are (eg. while entering/exiting as well).
*/
- private boolean isSplitActive() {
+ public boolean isSplitActive() {
return mSplits.mPrimary != null && mSplits.mSecondary != null
&& (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
|| mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
}
- private void addDivider(Configuration configuration) {
+ public void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
- private void removeDivider() {
+ public void removeDivider() {
if (mView != null) {
mView.onDividerRemoved();
}
mWindowManager.remove();
}
- private void update(Configuration configuration) {
+ public void update(Configuration configuration) {
final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
removeDivider();
@@ -358,11 +347,11 @@
mView.setHidden(isDividerHidden);
}
- void onTaskVanished() {
+ public void onTaskVanished() {
removeDivider();
}
- private void updateVisibility(final boolean visible) {
+ public void updateVisibility(final boolean visible) {
if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
mVisible = visible;
@@ -390,7 +379,7 @@
}
}
- private void setMinimized(final boolean minimized) {
+ public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
});
}
- private void setHomeMinimized(final boolean minimized) {
+ public void setHomeMinimized(final boolean minimized) {
if (DEBUG) {
Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
+ mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
}
}
- void setAdjustedForIme(boolean adjustedForIme) {
+ public void setAdjustedForIme(boolean adjustedForIme) {
if (mAdjustedForIme == adjustedForIme) {
return;
}
@@ -449,30 +438,30 @@
updateTouchable();
}
- private void updateTouchable() {
+ public void updateTouchable() {
mWindowManager.setTouchable(!mAdjustedForIme);
}
- private void onUndockingTask() {
+ public void onUndockingTask() {
if (mView != null) {
mView.onUndockingTask();
}
}
- private void onAppTransitionFinished() {
+ public void onAppTransitionFinished() {
if (mView == null) {
return;
}
mForcedResizableController.onAppTransitionFinished();
}
- private void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw) {
pw.print(" mVisible="); pw.println(mVisible);
pw.print(" mMinimized="); pw.println(mMinimized);
pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
}
- long getAnimDuration() {
+ public long getAnimDuration() {
float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE,
mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
return (long) (transitionDuration * transitionScale);
}
- void registerInSplitScreenListener(Consumer<Boolean> listener) {
+ public void registerInSplitScreenListener(Consumer<Boolean> listener) {
listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
}
- void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+ public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
synchronized (mDockedStackExistsListeners) {
for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
}
}
- private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+ public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.add(new WeakReference<>(listener));
}
}
- private boolean splitPrimaryTask() {
+ public boolean splitPrimaryTask() {
try {
if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
|| isSplitActive()) {
@@ -538,12 +527,12 @@
topRunningTask.taskId, true /* onTop */);
}
- private void dismissSplitToPrimaryTask() {
+ public void dismissSplitToPrimaryTask() {
startDismissSplit(true /* toPrimaryTask */);
}
/** Notifies the bounds of split screen changed. */
- void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+ public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.removeIf(wf -> {
BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
}
}
- void startEnterSplit() {
+ public void startEnterSplit() {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
// Set resizable directly here because applyEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
}
- void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+ public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
// Set resizable directly here because buildEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
}
- void finishEnterSplitTransition(boolean minimized) {
+ public void finishEnterSplitTransition(boolean minimized) {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
if (minimized) {
@@ -575,11 +564,11 @@
}
}
- void startDismissSplit(boolean toPrimaryTask) {
+ public void startDismissSplit(boolean toPrimaryTask) {
startDismissSplit(toPrimaryTask, false /* snapped */);
}
- void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+ public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mSplits.getSplitTransitions().dismissSplit(
mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
}
}
- void onDismissSplit() {
+ public void onDismissSplit() {
updateVisibility(false /* visible */);
mMinimized = false;
// Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
mImePositionProcessor.reset();
}
- void ensureMinimizedSplit() {
+ public void ensureMinimizedSplit() {
setHomeMinimized(true /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
}
}
- void ensureNormalSplit() {
+ public void ensureNormalSplit() {
setHomeMinimized(false /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
}
}
- LegacySplitDisplayLayout getSplitLayout() {
+ public LegacySplitDisplayLayout getSplitLayout() {
return mSplitLayout;
}
- WindowManagerProxy getWmProxy() {
+ public WindowManagerProxy getWmProxy() {
return mWindowManagerProxy;
}
- WindowContainerToken getSecondaryRoot() {
+ public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
return null;
}
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/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 94c6f01..c8f8987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -39,7 +39,6 @@
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -116,7 +115,7 @@
void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
WindowContainerTransaction t = new WindowContainerTransaction();
splitLayout.resizeSplits(position, t);
- new WindowOrganizer().applyTransaction(t);
+ applySyncTransaction(t);
}
boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
* 3 button navigation mode only
*/
void registerGestureCallback(OneHandedGestureEventCallback callback);
-
- /**
- * Dump one handed status.
- */
- void dump(@NonNull PrintWriter pw);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
/**
- * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+ * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
@Nullable
- public static OneHanded create(
+ public static OneHandedController create(
Context context, DisplayController displayController,
TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
return new OneHandedController(context, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
- taskStackListener, mainExecutor, mainHandler).mImpl;
+ taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
@@ -228,6 +228,10 @@
mAccessibilityStateChangeListener);
}
+ public OneHanded asOneHanded() {
+ return mImpl;
+ }
+
/**
* Set one handed enabled or disabled when user update settings
*/
@@ -468,7 +472,7 @@
}
}
- private void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
OneHandedController.this.registerGestureCallback(callback);
});
}
-
- @Override
- public void dump(@NonNull PrintWriter pw) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.dump(pw);
- });
- }
}
}
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 ad6f435..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,7 @@
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;
@@ -131,7 +131,7 @@
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mSplitScreenOptional;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -207,7 +207,7 @@
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
@NonNull PipTransitionController pipTransitionController,
- Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<LegacySplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -584,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;
@@ -1047,7 +1053,8 @@
}
/**
- * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+ * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+ * screen.
*
* @param destinationBoundsOut contain the updated destination bounds if applicable
* @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1064,7 @@
return false;
}
- LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+ LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
if (!legacySplitScreen.isDividerVisible()) {
// fail early if system is not in split screen mode
return false;
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..4bcd76f 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
@@ -325,7 +325,7 @@
+ " callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenuWithoutResize();
+ mMenuController.hideMenu(false /* animate */, false /* resize */);
mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
}
@@ -338,7 +338,7 @@
Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenuWithoutResize();
+ mMenuController.hideMenu(true /* animate*/, false /* resize */);
mPipTaskOrganizer.removePip();
}
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 53571ff..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,
@@ -558,8 +558,8 @@
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
}
- mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
- mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
} else {
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..f3875ea 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
@@ -63,8 +63,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
@@ -876,6 +877,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);
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/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
- /**
- * 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 taskBounds task bounds to place the restart button in.
- * @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.
- */
- void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
- @Nullable IBinder sizeCompatActivity,
- @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
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 286c3b6..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,144 +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
- final SizeCompatUI mImpl = new SizeCompatUIImpl();
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;
- /** Creates the {@link SizeCompatUIController}. */
- public static SizeCompatUI create(Context context,
+ public SizeCompatUIController(Context context,
DisplayController displayController,
DisplayImeController imeController,
- ShellExecutor mainExecutor) {
- return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
- .mImpl;
- }
-
- @VisibleForTesting
- 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);
}
- private 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);
}
}
@@ -178,14 +200,13 @@
return context;
}
- private class SizeCompatUIImpl implements SizeCompatUI {
- @Override
- public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
- @Nullable IBinder sizeCompatActivity,
- @Nullable ShellTaskOrganizer.TaskListener taskListener) {
- mMainExecutor.execute(() ->
- SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
- taskBounds, sizeCompatActivity, taskListener));
+ 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/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f3f2fc3..45d5515 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -31,23 +31,21 @@
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.Slog;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
+import android.view.Window;
+import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
-import com.android.internal.policy.PhoneWindow;
import java.util.List;
/**
* Util class to create the view for a splash screen content.
+ * @hide
*/
-class SplashscreenContentDrawer {
+public class SplashscreenContentDrawer {
private static final String TAG = StartingSurfaceDrawer.TAG;
private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
@@ -58,15 +56,24 @@
// also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
private final Context mContext;
- private int mIconSize;
+ private final int mMaxIconAnimationDuration;
- SplashscreenContentDrawer(Context context) {
+ private int mIconSize;
+ private int mBrandingImageWidth;
+ private int mBrandingImageHeight;
+
+ SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) {
mContext = context;
+ mMaxIconAnimationDuration = maxIconAnimationDuration;
}
private void updateDensity() {
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.starting_surface_icon_size);
+ mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
+ mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
}
private int getSystemBGColor() {
@@ -83,48 +90,92 @@
return new ColorDrawable(getSystemBGColor());
}
- View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes,
+ SplashScreenView makeSplashScreenContentView(Window win, Context context, int iconRes,
int splashscreenContentResId) {
updateDensity();
- win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// splash screen content will be deprecated after S.
- final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
+ final SplashScreenView ssc =
+ makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
if (ssc != null) {
return ssc;
}
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- typedArray.recycle();
+ final SplashScreenWindowAttrs attrs =
+ SplashScreenWindowAttrs.createWindowAttrs(context);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final Drawable themeBGDrawable;
- if (resId == 0) {
+
+ if (attrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
+ } else if (attrs.mWindowBgResId != 0) {
+ themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ } else {
Slog.w(TAG, "Window background not exist!");
themeBGDrawable = createDefaultBackgroundDrawable();
- } else {
- themeBGDrawable = context.getDrawable(resId);
}
- final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
- : context.getPackageManager().getDefaultActivityIcon();
+ final int animationDuration;
+ final Drawable iconDrawable;
+ if (attrs.mReplaceIcon != null) {
+ iconDrawable = attrs.mReplaceIcon;
+ animationDuration = Math.max(0,
+ Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration));
+ } else {
+ iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
+ : context.getPackageManager().getDefaultActivityIcon();
+ animationDuration = 0;
+ }
// TODO (b/173975965) Tracking the performance on improved splash screen.
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
return builder
- .setPhoneWindow(win)
+ .setWindow(win)
.setContext(context)
.setThemeDrawable(themeBGDrawable)
- .setIconDrawable(iconDrawable).build();
+ .setIconDrawable(iconDrawable)
+ .setIconAnimationDuration(animationDuration)
+ .setBrandingDrawable(attrs.mBrandingImage).build();
+ }
+
+ private static class SplashScreenWindowAttrs {
+ private int mWindowBgResId = 0;
+ private int mWindowBgColor = Color.TRANSPARENT;
+ private Drawable mReplaceIcon = null;
+ private Drawable mBrandingImage = null;
+ private int mAnimationDuration = 0;
+
+ static SplashScreenWindowAttrs createWindowAttrs(Context context) {
+ final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
+ return attrs;
+ }
}
private class StartingWindowViewBuilder {
- // materials
private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
- private PhoneWindow mPhoneWindow;
+ private Window mWindow;
+ private int mIconAnimationDuration;
private Context mContext;
+ private Drawable mBrandingDrawable;
// result
private boolean mBuildComplete = false;
- private View mCachedResult;
+ private SplashScreenView mCachedResult;
private int mThemeColor;
private Drawable mFinalIconDrawable;
private float mScale = 1f;
@@ -141,8 +192,20 @@
return this;
}
- StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
- mPhoneWindow = window;
+ StartingWindowViewBuilder setWindow(Window window) {
+ mWindow = window;
+ mBuildComplete = false;
+ return this;
+ }
+
+ StartingWindowViewBuilder setIconAnimationDuration(int iconAnimationDuration) {
+ mIconAnimationDuration = iconAnimationDuration;
+ mBuildComplete = false;
+ return this;
+ }
+
+ StartingWindowViewBuilder setBrandingDrawable(Drawable branding) {
+ mBrandingDrawable = branding;
mBuildComplete = false;
return this;
}
@@ -153,11 +216,11 @@
return this;
}
- View build() {
+ SplashScreenView build() {
if (mBuildComplete) {
return mCachedResult;
}
- if (mPhoneWindow == null || mContext == null) {
+ if (mWindow == null || mContext == null) {
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
@@ -173,7 +236,7 @@
mFinalIconDrawable = mIconDrawable;
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
- mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable);
+ mCachedResult = fillViewWithIcon(mWindow, mContext, iconSize, mFinalIconDrawable);
mBuildComplete = true;
return mCachedResult;
}
@@ -249,25 +312,26 @@
return true;
}
- private View fillViewWithIcon(PhoneWindow win, Context context,
+ private SplashScreenView fillViewWithIcon(Window win, Context context,
int iconSize, Drawable iconDrawable) {
- final StartingSurfaceWindowView surfaceWindowView =
- new StartingSurfaceWindowView(context, iconSize);
- surfaceWindowView.setBackground(new ColorDrawable(mThemeColor));
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
+ builder.setIconSize(iconSize).setBackgroundColor(mThemeColor);
if (iconDrawable != null) {
- surfaceWindowView.setIconDrawable(iconDrawable);
+ builder.setCenterViewDrawable(iconDrawable);
}
+ builder.setAnimationDuration(mIconAnimationDuration);
+ if (mBrandingDrawable != null) {
+ builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth,
+ mBrandingImageHeight);
+ }
+ final SplashScreenView splashScreenView = builder.build();
if (DEBUG) {
- Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView);
+ Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
- win.setContentView(surfaceWindowView);
- makeSystemUIColorsTransparent(win);
- return surfaceWindowView;
- }
-
- private void makeSystemUIColorsTransparent(PhoneWindow win) {
- win.setStatusBarColor(Color.TRANSPARENT);
- win.setNavigationBarColor(Color.TRANSPARENT);
+ win.setContentView(splashScreenView);
+ splashScreenView.cacheRootWindow(win);
+ splashScreenView.makeSystemUIColorsTransparent();
+ return splashScreenView;
}
}
@@ -298,8 +362,8 @@
return root < 0.1;
}
- private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx,
- int splashscreenContentResId) {
+ private static SplashScreenView makeSplashscreenContentDrawable(Window win,
+ Context ctx, int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
@@ -316,7 +380,8 @@
if (drawable == null) {
return null;
}
- View view = new View(ctx);
+ SplashScreenView view = new SplashScreenView(ctx);
+ view.setNotCopyable();
view.setBackground(drawable);
win.setContentView(view);
return view;
@@ -532,34 +597,4 @@
}
}
}
-
- private static class StartingSurfaceWindowView extends FrameLayout {
- // TODO animate the icon view
- private final View mIconView;
-
- StartingSurfaceWindowView(Context context, int iconSize) {
- super(context);
-
- final boolean emptyIcon = iconSize == 0;
- if (emptyIcon) {
- mIconView = null;
- } else {
- mIconView = new View(context);
- FrameLayout.LayoutParams params =
- new FrameLayout.LayoutParams(iconSize, iconSize);
- params.gravity = Gravity.CENTER;
- addView(mIconView, params);
- }
- setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- }
-
- // TODO support animatable icon
- void setIconDrawable(Drawable icon) {
- if (mIconView != null) {
- mIconView.setBackground(icon);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index f374922..5332291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -43,6 +43,8 @@
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
+import android.window.SplashScreenView;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
@@ -63,7 +65,6 @@
* class to remove the starting window of the Task.
* @hide
*/
-
public class StartingSurfaceDrawer {
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
static final boolean DEBUG_SPLASH_SCREEN = false;
@@ -81,7 +82,10 @@
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mMainExecutor = mainExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(context);
+
+ final int maxIconAnimDuration = context.getResources().getInteger(
+ com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration);
}
private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
@@ -193,9 +197,8 @@
public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
final PreferredStartingTypeHelper helper =
new PreferredStartingTypeHelper(windowInfo);
- final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) {
- addSplashScreenStartingWindow(runningTaskInfo, appToken);
+ addSplashScreenStartingWindow(windowInfo, appToken);
} else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = helper.mSnapshot;
makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
@@ -203,11 +206,13 @@
// If prefer don't show, then don't show!
}
- private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) {
+ private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ final RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = taskInfo.topActivityInfo;
if (activityInfo == null) {
return;
}
+
final int displayId = taskInfo.displayId;
if (activityInfo.packageName == null) {
return;
@@ -222,11 +227,11 @@
}
Context context = mContext;
- int theme = activityInfo.getThemeResource();
- if (theme == 0) {
- // replace with the default theme if the application didn't set
- theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight;
- }
+ // replace with the default theme if the application didn't set
+ final int theme = windowInfo.splashScreenThemeResId != 0
+ ? windowInfo.splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
if (DEBUG_SPLASH_SCREEN) {
Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
+ ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
@@ -352,9 +357,10 @@
}
params.setTitle("Splash Screen " + activityInfo.packageName);
- final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win,
- context, iconRes, splashscreenContentResId[0]);
- if (contentView == null) {
+ final SplashScreenView splashScreenView =
+ mSplashscreenContentDrawer.makeSplashScreenContentView(win, context, iconRes,
+ splashscreenContentResId[0]);
+ if (splashScreenView == null) {
Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!");
return;
}
@@ -366,7 +372,7 @@
+ activityInfo.packageName + " / " + appToken + ": " + view);
}
final WindowManager wm = context.getSystemService(WindowManager.class);
- postAddWindow(taskInfo.taskId, appToken, view, wm, params);
+ postAddWindow(taskInfo.taskId, appToken, view, wm, params, splashScreenView);
}
/**
@@ -379,7 +385,7 @@
snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */);
mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
final StartingWindowRecord tView =
- new StartingWindowRecord(null/* decorView */, surface);
+ new StartingWindowRecord(null/* decorView */, surface, null /* splashScreenView */);
mStartingWindowRecords.put(taskId, tView);
}
@@ -393,37 +399,60 @@
removeWindowSynced(taskId);
}
- protected void postAddWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params) {
- boolean shouldSaveView = true;
- try {
- wm.addView(view, params);
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, appToken + " failed creating starting window", e);
- shouldSaveView = false;
- } finally {
- if (view != null && view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- wm.removeViewImmediate(view);
- shouldSaveView = false;
- }
+ /**
+ * Called when the Task wants to copy the splash screen.
+ * @param taskId
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+ SplashScreenViewParcelable parcelable;
+ if (preView != null && preView.mContentView != null
+ && preView.mContentView.isCopyable()) {
+ parcelable = new SplashScreenViewParcelable(preView.mContentView);
+ } else {
+ parcelable = null;
}
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.v(TAG, "Copying splash screen window view for task: " + taskId
+ + " parcelable? " + parcelable);
+ }
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
- if (shouldSaveView) {
- removeWindowSynced(taskId);
- mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
- final StartingWindowRecord tView =
- new StartingWindowRecord(view, null /* TaskSnapshotWindow */);
- mStartingWindowRecords.put(taskId, tView);
- }
+ protected void postAddWindow(int taskId, IBinder appToken,
+ View view, WindowManager wm, WindowManager.LayoutParams params,
+ SplashScreenView splashScreenView) {
+ mMainExecutor.execute(() -> {
+ boolean shouldSaveView = true;
+ try {
+ wm.addView(view, params);
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, appToken + " failed creating starting window", e);
+ shouldSaveView = false;
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ removeWindowSynced(taskId);
+ mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+ final StartingWindowRecord tView = new StartingWindowRecord(view,
+ null /* TaskSnapshotWindow */, splashScreenView);
+ splashScreenView.startIntroAnimation();
+ mStartingWindowRecords.put(taskId, tView);
+ }
+ });
}
protected void removeWindowSynced(int taskId) {
@@ -459,10 +488,13 @@
private static class StartingWindowRecord {
private final View mDecorView;
private final TaskSnapshotWindow mTaskSnapshotWindow;
+ private final SplashScreenView mContentView;
- StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) {
+ StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow,
+ SplashScreenView splashScreenView) {
mDecorView = decorView;
mTaskSnapshotWindow = taskSnapshotWindow;
+ mContentView = splashScreenView;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index a6f44ef..b7fd3cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -43,6 +43,7 @@
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
@@ -498,7 +499,7 @@
}
}
- @ExternalThread
+ @BinderThread
static class Window extends BaseIWindow {
private TaskSnapshotWindow mOuter;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 111362a..ecc066b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -24,7 +24,6 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -113,7 +112,7 @@
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
- uiDevice.closePipWindow()
+ closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index a14b46e..d56ed02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -50,8 +48,7 @@
@JvmStatic
fun getParams(): List<Array<Any>> {
val testApp = FixedAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
@@ -97,7 +94,7 @@
}
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 99a40da..ff31ba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -50,9 +48,8 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(
- eachRun = true, stringExtras = emptyMap())
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true,
+ stringExtras = emptyMap()) { configuration ->
transitions {
pipApp.clickEnterPipButton()
pipApp.expandPipWindow(wmHelper)
@@ -92,7 +89,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 7576e24..f054e64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
@@ -48,8 +46,7 @@
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val imeApp = ImeAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = false)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = false) { configuration ->
setup {
test {
imeApp.launchViaIntent(wmHelper)
@@ -90,7 +87,7 @@
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index adab5e8..ade65ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
@@ -55,8 +53,7 @@
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val fixedApp = FixedAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = false)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = false) { configuration ->
setup {
test {
fixedApp.launchViaIntent(wmHelper)
@@ -112,8 +109,7 @@
}
return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
- baseConfig, testSpec,
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 4b826ff..f2d5899 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
@@ -110,7 +107,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 62e8221..1b44377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
@@ -111,7 +108,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
index eb7bae1..b1e404e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -22,8 +22,6 @@
import android.view.Surface
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
@@ -83,10 +81,6 @@
}
test {
removeAllTasksButHome()
-
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
pipApp.exit()
}
}
@@ -98,11 +92,13 @@
*
* @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
* @param stringExtras Arguments to pass to the PIP launch intent
+ * @param extraSpec Addicional segment of flicker specification
*/
@JvmOverloads
- fun getTransitionLaunch(
+ open fun getTransition(
eachRun: Boolean,
- stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+ stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+ extraSpec: FlickerBuilder.(Bundle) -> Unit = {}
): FlickerBuilder.(Bundle) -> Unit {
return { configuration ->
setupAndTeardown(this, configuration)
@@ -135,6 +131,8 @@
removeAllTasksButHome()
}
}
+
+ extraSpec(this, configuration)
}
}
}
\ No newline at end of file
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 80ea9b9..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;
@@ -51,7 +52,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import org.junit.Before;
import org.junit.Test;
@@ -76,7 +78,9 @@
@Mock
private Context mContext;
@Mock
- private SizeCompatUI mSizeCompatUI;
+ private SizeCompatUIController mSizeCompatUI;
+ @Mock
+ private StartingSurfaceDrawer mStartingSurfaceDrawer;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -112,7 +116,7 @@
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mSizeCompatUI));
+ mSizeCompatUI, mStartingSurfaceDrawer));
}
@Test
@@ -279,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;
@@ -290,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/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import org.junit.Before;
@@ -70,7 +71,7 @@
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
- @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+ @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
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 98f01ff..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.mImpl.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.mImpl.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.mImpl.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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index c9537af..de7d6c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -40,6 +40,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,7 +80,8 @@
@Override
protected void postAddWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params) {
+ View view, WindowManager wm, WindowManager.LayoutParams params,
+ SplashScreenView splashScreenView) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
@@ -125,7 +127,8 @@
createWindowInfo(taskId, android.R.style.Theme);
mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ verify(mStartingSurfaceDrawer).postAddWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(), any());
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId);
@@ -142,7 +145,8 @@
createWindowInfo(taskId, 0);
mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ verify(mStartingSurfaceDrawer).postAddWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(), any());
assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
}
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/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 0e338f3..2db3ace 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,10 +30,11 @@
namespace android {
-MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
- std::string_view filePath, int ttcIndex,
+MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
+ size_t fontSize, std::string_view filePath, int ttcIndex,
const std::vector<minikin::FontVariation>& axes)
: mTypeface(std::move(typeface))
+ , mSourceId(sourceId)
, mFontData(fontData)
, mFontSize(fontSize)
, mTtcIndex(ttcIndex)
@@ -141,8 +142,8 @@
sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
- return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath,
- ttcIndex, variations);
+ return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
+ mFilePath, ttcIndex, variations);
}
// hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 77a2142..de9a5c2 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -30,7 +30,7 @@
class ANDROID_API MinikinFontSkia : public minikin::MinikinFont {
public:
- MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
+ MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
std::string_view filePath, int ttcIndex,
const std::vector<minikin::FontVariation>& axes);
@@ -62,6 +62,7 @@
const std::vector<minikin::FontVariation>& GetAxes() const;
std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
const std::vector<minikin::FontVariation>&) const;
+ int GetSourceId() const override { return mSourceId; }
static uint32_t packFontFlags(const SkFont&);
static void unpackFontFlags(SkFont*, uint32_t fontFlags);
@@ -73,6 +74,7 @@
private:
sk_sp<SkTypeface> mTypeface;
+ int mSourceId;
// A raw pointer to the font data - it should be owned by some other object with
// lifetime at least as long as this object.
const void* mFontData;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 03f1d62..5a9d250 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -185,9 +185,9 @@
sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
- std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
- std::move(typeface), data, st.st_size, kRobotoFont, 0,
- std::vector<minikin::FontVariation>());
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
+ 0, std::vector<minikin::FontVariation>());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 2e85840..ce5ac38 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -17,15 +17,16 @@
#undef LOG_TAG
#define LOG_TAG "Minikin"
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include "FontUtils.h"
+#include "GraphicsJNI.h"
#include "SkData.h"
#include "SkFontMgr.h"
#include "SkRefCnt.h"
#include "SkTypeface.h"
-#include "GraphicsJNI.h"
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
#include "Utils.h"
-#include "FontUtils.h"
+#include "fonts/Font.h"
#include <hwui/MinikinSkia.h>
#include <hwui/Typeface.h>
@@ -35,6 +36,12 @@
#include <memory>
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// The following JNI methods are kept only for compatibility reasons due to hidden API accesses.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
namespace android {
struct NativeFamilyBuilder {
@@ -125,8 +132,8 @@
return false;
}
std::shared_ptr<minikin::MinikinFont> minikinFont =
- std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, "", ttcIndex,
- builder->axes);
+ std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
+ fontSize, "", ttcIndex, builder->axes);
minikin::Font::Builder fontBuilder(minikinFont);
if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index fa1752c..a48d7f7 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -64,8 +64,8 @@
sk_sp<SkImage> image = android::bitmap::toBitmap(bitmapHandle).makeImage();
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImageFilter> bitmapFilter =
- SkImageFilters::Image(image, srcRect, dstRect, kLow_SkFilterQuality);
+ sk_sp<SkImageFilter> bitmapFilter = SkImageFilters::Image(
+ image, srcRect, dstRect, SkSamplingOptions(SkFilterMode::kLinear));
return reinterpret_cast<jlong>(bitmapFilter.release());
}
@@ -150,4 +150,4 @@
android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
gRenderEffectMethods, NELEM(gRenderEffectMethods));
return 0;
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 1dc5cd9..2e4d7f62 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -239,14 +239,12 @@
static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
ScopedUtfChars strSksl(env, sksl);
- auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
- sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
- if (effect.get() == nullptr) {
- const auto& err = std::get<1>(result);
- doThrowIAE(env, err.c_str());
+ auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()), SkRuntimeEffect::Options{});
+ if (result.effect.get() == nullptr) {
+ doThrowIAE(env, result.errorText.c_str());
return 0;
}
- return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect)));
+ return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect)));
}
static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..251323d 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,48 @@
env->SetStaticObjectField(cls, fid, typeface);
}
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+ return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+ std::shared_ptr<minikin::FontFamily> family =
+ toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+ return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
+
+// Regular JNI
+static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
+ ScopedUtfChars filePath(env, jFilePath);
+ makeSkDataCached(filePath.c_str(), false /* fs verity */);
+}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
- { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
- { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
- (void*)Typeface_createFromTypefaceWithExactStyle },
- { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
- (void*)Typeface_createFromTypefaceWithVariation },
- { "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias },
- { "nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc },
- { "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
- { "nativeGetWeight", "(J)I", (void*)Typeface_getWeight },
- { "nativeCreateFromArray", "([JJII)J",
- (void*)Typeface_createFromArray },
- { "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
- { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes },
- { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
- (void*)Typeface_registerGenericFamily },
- { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
- { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
- { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
- (void*)Typeface_forceSetStaticFinalField },
+ {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+ {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+ (void*)Typeface_createFromTypefaceWithExactStyle},
+ {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+ (void*)Typeface_createFromTypefaceWithVariation},
+ {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+ {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+ {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+ {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+ {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+ {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+ {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+ {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+ (void*)Typeface_registerGenericFamily},
+ {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+ {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+ {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+ (void*)Typeface_forceSetStaticFinalField},
+ {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+ {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
+ {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
};
int register_android_graphics_Typeface(JNIEnv* env)
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/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index c8471a9..5a972f5 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -137,12 +137,9 @@
sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args);
std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
- std::move(newTypeface),
- minikinSkia->GetFontData(),
- minikinSkia->GetFontSize(),
- minikinSkia->getFilePath(),
- minikinSkia->GetFontIndex(),
- builder->axes);
+ std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
+ minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
+ builder->axes);
std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
.setWeight(weight)
.setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -279,6 +276,12 @@
return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
}
+// Critical Native
+static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
+ return font->font->typeface()->GetSourceId();
+}
+
// Fast Native
static jlong FontFileUtil_getFontRevision(JNIEnv* env, jobject, jobject buffer, jint index) {
NPE_CHECK_RETURN_ZERO(env, buffer);
@@ -369,6 +372,7 @@
{"nGetIndex", "(J)I", (void*)Font_getIndex},
{"nGetAxisCount", "(J)I", (void*)Font_getAxisCount},
{"nGetAxisInfo", "(JI)J", (void*)Font_getAxisInfo},
+ {"nGetSourceId", "(J)I", (void*)Font_getSourceId},
};
static const JNINativeMethod gFontFileUtilMethods[] = {
@@ -409,10 +413,15 @@
if (face == nullptr) {
return nullptr;
}
- return std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize,
+ return std::make_shared<MinikinFontSkia>(std::move(face), getNewSourceId(), fontPtr, fontSize,
fontPath, ttcIndex, axes);
}
+int getNewSourceId() {
+ static std::atomic<int> sSourceId = {0};
+ return sSourceId++;
+}
+
} // namespace fonts
} // namespace android
diff --git a/libs/hwui/jni/fonts/Font.h b/libs/hwui/jni/fonts/Font.h
index b5d20bf..4bf60ee 100644
--- a/libs/hwui/jni/fonts/Font.h
+++ b/libs/hwui/jni/fonts/Font.h
@@ -33,6 +33,8 @@
sk_sp<SkData>&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize,
int ttcIndex, const std::vector<minikin::FontVariation>& axes);
+int getNewSourceId();
+
} // namespace fonts
} // namespace android
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/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 5d2aa2f..ab23448 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
std::shared_ptr<minikin::MinikinFont> font =
- std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0,
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
std::vector<minikin::FontVariation>());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
name: "libincident_test",
test_config: "AndroidTest.xml",
defaults: ["libincidentpriv_defaults"],
- test_suites: ["device-tests", "mts"],
+ test_suites: ["device-tests", "mts-statsd"],
compile_multilib: "both",
multilib: {
lib64: {
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/TEST_MAPPING b/media/TEST_MAPPING
index cf2f0f0..a7ed091 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -10,7 +10,7 @@
"include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
},
{
- "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
+ "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
}
]
}
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/DrmInitData.java b/media/java/android/media/DrmInitData.java
index 85b4ba5..3c48f8f 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -19,6 +19,7 @@
import android.media.MediaDrm;
import java.util.Arrays;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -94,9 +95,9 @@
* @param data The initialization data.
*/
public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
- this.uuid = uuid;
- this.mimeType = mimeType;
- this.data = data;
+ this.uuid = Objects.requireNonNull(uuid);
+ this.mimeType = Objects.requireNonNull(mimeType);
+ this.data = Objects.requireNonNull(data);
}
@Override
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 02fa94c..5a7ff7f 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -33,7 +33,8 @@
float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
oneway void stop(IBinder token);
boolean isPlaying(IBinder token);
- oneway void setPlaybackProperties(IBinder token, float volume, boolean looping);
+ oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
+ boolean hapticGeneratorEnabled);
/** Used for Notification sound playback. */
oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
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/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 1a49b85..67f1660 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
-import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1154,6 +1153,22 @@
public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
/**
+ * An optional key describing the opto-electronic transfer function
+ * requested for the output video content.
+ *
+ * The associated value is an integer: 0 if unspecified, or one of the
+ * COLOR_TRANSFER_ values. When unspecified the component will not touch the
+ * video content; otherwise the component will tone-map the raw video frame
+ * to match the requested transfer function.
+ *
+ * After configure, component's input format will contain this key to note
+ * whether the request is supported or not. If the value in the input format
+ * is the same as the requested value, the request is supported. The value
+ * is set to 0 if unsupported.
+ */
+ public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
+
+ /**
* A key describing a unique ID for the content of a media track.
*
* <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings
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/Ringtone.java b/media/java/android/media/Ringtone.java
index bd783ce..79d505e 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -24,6 +24,7 @@
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
+import android.media.audiofx.HapticGenerator;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -77,6 +78,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private MediaPlayer mLocalPlayer;
private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+ private HapticGenerator mHapticGenerator;
@UnsupportedAppUsage
private Uri mUri;
@@ -89,6 +91,7 @@
// playback properties, use synchronized with mPlaybackSettingsLock
private boolean mIsLooping = false;
private float mVolume = 1.0f;
+ private boolean mHapticGeneratorEnabled = false;
private final Object mPlaybackSettingsLock = new Object();
/** {@hide} */
@@ -197,15 +200,50 @@
}
/**
+ * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
+ * only be enabled on devices that support the effect.
+ *
+ * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
+ * @see android.media.audiofx.HapticGenerator#isAvailable()
+ */
+ public boolean setHapticGeneratorEnabled(boolean enabled) {
+ if (!HapticGenerator.isAvailable()) {
+ return false;
+ }
+ synchronized (mPlaybackSettingsLock) {
+ mHapticGeneratorEnabled = enabled;
+ applyPlaybackProperties_sync();
+ }
+ return true;
+ }
+
+ /**
+ * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
+ * @return true if the HapticGenerator is enabled.
+ */
+ public boolean isHapticGeneratorEnabled() {
+ synchronized (mPlaybackSettingsLock) {
+ return mHapticGeneratorEnabled;
+ }
+ }
+
+ /**
* Must be called synchronized on mPlaybackSettingsLock
*/
private void applyPlaybackProperties_sync() {
if (mLocalPlayer != null) {
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
+ if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+ mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+ }
+ if (mHapticGenerator != null) {
+ mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+ }
} else if (mAllowRemote && (mRemotePlayer != null)) {
try {
- mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+ mRemotePlayer.setPlaybackProperties(
+ mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
} catch (RemoteException e) {
Log.w(TAG, "Problem setting playback properties: ", e);
}
@@ -413,6 +451,10 @@
private void destroyLocalPlayer() {
if (mLocalPlayer != null) {
+ if (mHapticGenerator != null) {
+ mHapticGenerator.release();
+ mHapticGenerator = null;
+ }
mLocalPlayer.setOnCompletionListener(null);
mLocalPlayer.reset();
mLocalPlayer.release();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
* Pauses TV program recording in the current recording session.
*
* @param params A set of extra parameters which might be handled with this event.
+ * {@link TvRecordingClient#pauseRecording(Bundle)}.
*/
void pauseRecording(@NonNull Bundle params) {
if (mToken == null) {
@@ -2543,6 +2544,7 @@
* Resumes TV program recording in the current recording session.
*
* @param params A set of extra parameters which might be handled with this event.
+ * {@link TvRecordingClient#resumeRecording(Bundle)}.
*/
void resumeRecording(@NonNull Bundle params) {
if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
"libstagefright_foundation_headers",
],
+ // TunerService is a system service required for Tuner feature.
+ // TunerJNI is a client of TunerService so we build the dependency here.
+ required: ["mediatuner"],
+
export_include_dirs: ["."],
cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
#include <android/binder_parcel_utils.h>
#include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
using Status = ::ndk::ScopedAStatus;
@@ -37,6 +38,7 @@
} else if (s.isOk()) {
return Result::SUCCESS;
}
+ ALOGE("Aidl exception code %s", s.getDescription().c_str());
return Result::UNKNOWN_ERROR;
}
};
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 8b4ca37..f31d465 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
namespace android {
@@ -261,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;
}
@@ -480,7 +481,7 @@
case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
int size = ipAddr.srcIpAddress.v4().size();
srcIpAddress.isIpV6 = false;
- srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+ srcIpAddress.addr.resize(size);
copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
srcIpAddress.addr.begin());
break;
@@ -493,8 +494,6 @@
srcIpAddress.addr.begin());
break;
}
- default:
- break;
}
switch (ipAddr.dstIpAddress.getDiscriminator()) {
case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
dstIpAddress.addr.begin());
break;
}
- default:
- break;
}
}
@@ -696,8 +693,6 @@
getHidlRestartEvent(filterEvents, eventExt);
break;
}
- default:
- break;
}
}
@@ -883,19 +878,18 @@
DemuxFilterEventExt& eventExt) {
auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
eventExt.events.resize(1);
+ DemuxFilterMonitorEvent monitorEvent;
switch (monitor.getTag()) {
case TunerFilterMonitorEvent::scramblingStatus: {
- eventExt.events[0].monitorEvent().scramblingStatus(
- static_cast<ScramblingStatus>(monitor.scramblingStatus));
+ monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+ eventExt.events[0].monitorEvent(monitorEvent);
break;
}
case TunerFilterMonitorEvent::cid: {
- eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+ monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+ eventExt.events[0].monitorEvent(monitorEvent);
break;
}
- default:
- eventExt.events[0].noinit();
- break;
}
}
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..9e36642 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
using ::android::hardware::tv::tuner::V1_1::FrontendType;
namespace android {
/////////////// FrontendClient ///////////////////////
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
mTunerFrontend = tunerFrontend;
mAidlCallback = NULL;
mHidlCallback = NULL;
- mId = id;
mType = type;
}
@@ -104,6 +110,11 @@
mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
}
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+ mId = id;
+}
+
Result FrontendClient::tune(const FrontendSettings& settings,
const FrontendSettingsExt1_1& settingsExt1_1) {
if (mTunerFrontend != NULL) {
@@ -311,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;
}
@@ -333,13 +343,26 @@
}
int FrontendClient::getId() {
- return mId;
+ if (mTunerFrontend != NULL) {
+ Status s = mTunerFrontend->getFrontendId(&mId);
+ if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+ return mId;
+ }
+ ALOGE("Failed to getFrontendId from Tuner Frontend");
+ return -1;
+ }
+
+ if (mFrontend != NULL) {
+ return mId;
+ }
+
+ return -1;
}
vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
vector<FrontendStatus> hidlStatus;
for (TunerFrontendStatus s : aidlStatus) {
- FrontendStatus status;
+ FrontendStatus status = FrontendStatus();
switch (s.getTag()) {
case TunerFrontendStatus::isDemodLocked: {
status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +412,31 @@
}
case TunerFrontendStatus::modulation: {
auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+ FrontendModulationStatus modulation;
switch (mType) {
case (int)FrontendType::DVBC:
- status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBS:
- status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS:
- status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS3:
- status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
default:
@@ -466,7 +495,7 @@
}
case TunerFrontendStatus::hierarchy: {
status.hierarchy(static_cast<FrontendDvbtHierarchy>(
- s.get<TunerFrontendStatus::freqOffset>()));
+ s.get<TunerFrontendStatus::hierarchy>()));
hidlStatus.push_back(status);
break;
}
@@ -477,15 +506,16 @@
}
case TunerFrontendStatus::plpInfo: {
int size = s.get<TunerFrontendStatus::plpInfo>().size();
- status.plpInfo().resize(size);
+ hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
for (int i = 0; i < size; i++) {
auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
- status.plpInfo()[i] = {
+ info[i] = {
.plpId = (uint8_t)aidlInfo.plpId,
.isLocked = aidlInfo.isLocked,
.uec = (uint32_t)aidlInfo.uec,
};
}
+ status.plpInfo(info);
hidlStatus.push_back(status);
break;
}
@@ -503,52 +533,54 @@
FrontendStatusExt1_1 status;
switch (s.getTag()) {
case TunerFrontendStatus::modulations: {
+ vector<FrontendModulation> ms;
for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
- int size = status.modulations().size();
- status.modulations().resize(size + 1);
+ FrontendModulation m;
switch (mType) {
case (int)FrontendType::DVBC:
- status.modulations()[size].dvbc(
- static_cast<FrontendDvbcModulation>(aidlMod));
+ m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DVBS:
- status.modulations()[size].dvbs(
- static_cast<FrontendDvbsModulation>(aidlMod));
+ m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DVBT:
- status.modulations()[size].dvbt(
- static_cast<FrontendDvbtConstellation>(aidlMod));
+ m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBS:
- status.modulations()[size].isdbs(
- static_cast<FrontendIsdbsModulation>(aidlMod));
+ m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBS3:
- status.modulations()[size].isdbs3(
- static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBT:
- status.modulations()[size].isdbt(
- static_cast<FrontendIsdbtModulation>(aidlMod));
+ m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ATSC:
- status.modulations()[size].atsc(
- static_cast<FrontendAtscModulation>(aidlMod));
+ m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ATSC3:
- status.modulations()[size].atsc3(
- static_cast<FrontendAtsc3Modulation>(aidlMod));
+ m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DTMB:
- status.modulations()[size].dtmb(
- static_cast<FrontendDtmbModulation>(aidlMod));
+ m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+ ms.push_back(m);
break;
default:
- status.modulations().resize(size);
break;
}
}
- hidlStatus.push_back(status);
+ if (ms.size() > 0) {
+ status.modulations(ms);
+ hidlStatus.push_back(status);
+ }
break;
}
case TunerFrontendStatus::bers: {
@@ -571,25 +603,31 @@
}
case TunerFrontendStatus::bandwidth: {
auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+ FrontendBandwidth band;
switch (mType) {
case (int)FrontendType::ATSC3:
- status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+ band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBC:
- status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+ band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBT:
- status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+ band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+ band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+ band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
default:
@@ -599,17 +637,21 @@
}
case TunerFrontendStatus::interval: {
auto aidlInter = s.get<TunerFrontendStatus::interval>();
+ FrontendGuardInterval inter;
switch (mType) {
case (int)FrontendType::DVBT:
- status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+ inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+ inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+ inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
default:
@@ -619,19 +661,21 @@
}
case TunerFrontendStatus::transmissionMode: {
auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+ FrontendTransmissionMode trans;
switch (mType) {
case (int)FrontendType::DVBT:
- status.transmissionMode().dvbt(
- static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+ trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+ trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.transmissionMode().dtmb(
- static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+ trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
default:
@@ -650,28 +694,30 @@
break;
}
case TunerFrontendStatus::interleaving: {
+ vector<FrontendInterleaveMode> modes;
for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
- int size = status.interleaving().size();
- status.interleaving().resize(size + 1);
+ FrontendInterleaveMode mode;
switch (mType) {
case (int)FrontendType::DVBC:
- status.interleaving()[size].dvbc(
- static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+ mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
case (int)FrontendType::ATSC3:
- status.interleaving()[size].atsc3(
- static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+ mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
case (int)FrontendType::DTMB:
- status.interleaving()[size].dtmb(
- static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+ mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
default:
- status.interleaving().resize(size);
break;
}
}
- hidlStatus.push_back(status);
+ if (modes.size() > 0) {
+ status.interleaving(modes);
+ hidlStatus.push_back(status);
+ }
break;
}
case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +736,21 @@
}
case TunerFrontendStatus::rollOff: {
auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+ FrontendRollOff roll;
switch (mType) {
case (int)FrontendType::DVBS:
- status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+ roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS:
- status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+ roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS3:
- status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+ roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
struct FrontendClient : public RefBase {
public:
- FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+ FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
~FrontendClient();
/**
@@ -180,6 +180,7 @@
shared_ptr<ITunerFrontend> getAidlFrontend();
+ void setId(int id);
int getId();
private:
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/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
// Connect with Tuner Service.
::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
mTunerService = ITunerService::fromBinder(binder);
- // TODO: Remove after JNI migration is done.
- mTunerService = NULL;
if (mTunerService == NULL) {
ALOGE("Failed to get tuner service");
} else {
// TODO: b/178124017 update TRM in TunerService independently.
mTunerService->updateTunerResources();
+ mTunerService->getTunerHalVersion(&mTunerVersion);
}
}
@@ -115,7 +114,7 @@
if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
return NULL;
}
- return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+ return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
}
if (mTuner != NULL) {
@@ -127,8 +126,10 @@
if (res != Result::SUCCESS) {
return NULL;
}
- sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+ sp<FrontendClient> frontendClient = new FrontendClient(
+ NULL, (int)hidlInfo.type);
frontendClient->setHidlFrontend(hidlFrontend);
+ frontendClient->setId(id);
return frontendClient;
}
}
@@ -358,7 +359,7 @@
sp<ITuner> TunerClient::getHidlTuner() {
if (mTuner == NULL) {
- mTunerVersion = 0;
+ mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
if (mTuner == NULL) {
ALOGW("Failed to get tuner 1.0 service.");
} else {
- mTunerVersion = 1 << 16;
+ mTunerVersion = TUNER_HAL_VERSION_1_0;
}
} else {
mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
- mTunerVersion = ((1 << 16) | 1);
+ mTunerVersion = TUNER_HAL_VERSION_1_1;
}
}
return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
namespace android {
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
typedef enum {
FRONTEND,
LNB,
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 314bf29..7a18bd5 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -225,6 +225,7 @@
AStorageManager_unmountObb;
ASurfaceControl_create; # introduced=29
ASurfaceControl_createFromWindow; # introduced=29
+ ASurfaceControl_acquire; # introduced=31
ASurfaceControl_release; # introduced=29
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 189be80..c1b5f1d 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -185,10 +185,16 @@
return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
}
-void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+void ASurfaceControl_acquire(ASurfaceControl* aSurfaceControl) {
+ SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- SurfaceControl_release(surfaceControl.get());
+ SurfaceControl_acquire(surfaceControl);
+}
+
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
+ SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+
+ SurfaceControl_release(surfaceControl);
}
ASurfaceTransaction* ASurfaceTransaction_create() {
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
index 9afa5d1..92a792b 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -49,17 +49,6 @@
}
);
- // TODO: move outside of the connectivity JAR
- SystemServiceRegistry.registerContextAwareService(
- Context.VPN_MANAGEMENT_SERVICE,
- VpnManager.class,
- (context) -> {
- final ConnectivityManager cm = context.getSystemService(
- ConnectivityManager.class);
- return cm.createVpnManager();
- }
- );
-
SystemServiceRegistry.registerContextAwareService(
Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
ConnectivityDiagnosticsManager.class,
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index fbe15bb..92d7bf0 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -824,6 +824,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
+
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1069,106 +1070,55 @@
}
/**
- * Checks if a VPN app supports always-on mode.
- *
- * In order to support the always-on feature, an app has to
- * <ul>
- * <li>target {@link VERSION_CODES#N API 24} or above, and
- * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
- * meta-data field.
- * </ul>
- *
- * @param userId The identifier of the user for whom the VPN app is installed.
- * @param vpnPackage The canonical package name of the VPN app.
- * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+ * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
* @hide
*/
+ @Deprecated
public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
- try {
- return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage);
}
/**
- * Configures an always-on VPN connection through a specific application.
- * This connection is automatically granted and persisted after a reboot.
- *
- * <p>The designated package should declare a {@link VpnService} in its
- * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
- * otherwise the call will fail.
- *
- * @param userId The identifier of the user to set an always-on VPN for.
- * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
- * to remove an existing always-on VPN configuration.
- * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
- * {@code false} otherwise.
- * @param lockdownAllowlist The list of packages that are allowed to access network directly
- * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
- * this method must be called when a package that should be allowed is installed or
- * uninstalled.
- * @return {@code true} if the package is set as always-on VPN controller;
- * {@code false} otherwise.
+ * Calls VpnManager#setAlwaysOnVpnPackageForUser.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ @Deprecated
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
- try {
- return mService.setAlwaysOnVpnPackage(
- userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled,
+ lockdownAllowlist);
}
- /**
- * Returns the package name of the currently set always-on VPN application.
- * If there is no always-on VPN set, or the VPN is provided by the system instead
- * of by an app, {@code null} will be returned.
- *
- * @return Package name of VPN controller responsible for always-on VPN,
- * or {@code null} if none is set.
+ /**
+ * Calls VpnManager#getAlwaysOnVpnPackageForUser.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ @Deprecated
public String getAlwaysOnVpnPackageForUser(int userId) {
- try {
- return mService.getAlwaysOnVpnPackage(userId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getVpnManager().getAlwaysOnVpnPackageForUser(userId);
}
/**
- * @return whether always-on VPN is in lockdown mode.
- *
+ * Calls VpnManager#isVpnLockdownEnabled.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
* @hide
- **/
- @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ */
+ @Deprecated
public boolean isVpnLockdownEnabled(int userId) {
- try {
- return mService.isVpnLockdownEnabled(userId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
+ return getVpnManager().isVpnLockdownEnabled(userId);
}
/**
- * @return the list of packages that are allowed to access network when always-on VPN is in
- * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
- *
+ * Calls VpnManager#getVpnLockdownAllowlist.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
* @hide
- **/
- @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
- public List<String> getVpnLockdownWhitelist(int userId) {
- try {
- return mService.getVpnLockdownWhitelist(userId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ */
+ @Deprecated
+ public List<String> getVpnLockdownAllowlist(int userId) {
+ return getVpnManager().getVpnLockdownAllowlist(userId);
}
/**
@@ -1221,6 +1171,45 @@
}
/**
+ * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
+ * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
+ * but is still supported for backwards compatibility.
+ * <p>
+ * This type of VPN is assumed always to use the system default network, and must always declare
+ * exactly one underlying network, which is the network that was the default when the VPN
+ * connected.
+ * <p>
+ * Calling this method with {@code true} enables legacy behaviour, specifically:
+ * <ul>
+ * <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated
+ * {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the
+ * {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN
+ * connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network
+ * underlying the VPN.</li>
+ * <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state
+ * similarly replaced by the VPN network state.</li>
+ * <li>Information on current network interfaces passed to NetworkStatsService will not
+ * include any VPN interfaces.</li>
+ * </ul>
+ *
+ * @param enabled whether legacy lockdown VPN is enabled or disabled
+ *
+ * TODO: @SystemApi(client = MODULE_LIBRARIES)
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void setLegacyLockdownVpnEnabled(boolean enabled) {
+ try {
+ mService.setLegacyLockdownVpnEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
* other apps.
@@ -3180,20 +3169,13 @@
}
/**
- * If the LockdownVpn mechanism is enabled, updates the vpn
- * with a reload of its profile.
- *
- * @return a boolean with {@code} indicating success
- *
- * <p>This method can only be called by the system UID
- * {@hide}
+ * Calls VpnManager#updateLockdownVpn.
+ * @deprecated TODO: remove when callers have migrated to VpnManager.
+ * @hide
*/
+ @Deprecated
public boolean updateLockdownVpn() {
- try {
- return mService.updateLockdownVpn();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getVpnManager().updateLockdownVpn();
}
/**
@@ -4557,6 +4539,8 @@
try {
mService.factoryReset();
mTetheringManager.stopAllTethering();
+ // TODO: Migrate callers to VpnManager#factoryReset.
+ getVpnManager().factoryReset();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4850,9 +4834,13 @@
return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
}
- /** @hide */
- public VpnManager createVpnManager() {
- return new VpnManager(mContext, mService);
+ /**
+ * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a
+ * private final mVpnManager because ConnectivityManager is initialized before VpnManager.
+ * @hide TODO: remove.
+ */
+ public VpnManager getVpnManager() {
+ return mContext.getSystemService(VpnManager.class);
}
/** @hide */
@@ -4886,15 +4874,6 @@
}
}
- private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
- try {
- mService.setOemNetworkPreference(preference);
- } catch (RemoteException e) {
- Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
- throw e.rethrowFromSystemServer();
- }
- }
-
@NonNull
private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
@@ -5096,4 +5075,60 @@
sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler));
}
+
+ /**
+ * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor,
+ * OnSetOemNetworkPreferenceListener)}.
+ * @hide
+ */
+ @SystemApi
+ public interface OnSetOemNetworkPreferenceListener {
+ /**
+ * Called when setOemNetworkPreference() successfully completes.
+ */
+ void onComplete();
+ }
+
+ /**
+ * Used by automotive devices to set the network preferences used to direct traffic at an
+ * application level as per the given OemNetworkPreferences. An example use-case would be an
+ * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+ * vehicle via a particular network.
+ *
+ * Calling this will overwrite the existing preference.
+ *
+ * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+ * @param executor the executor on which listener will be invoked.
+ * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to
+ * communicate completion of setOemNetworkPreference(). This will only be
+ * called once upon successful completion of setOemNetworkPreference().
+ * @throws IllegalArgumentException if {@code preference} contains invalid preference values.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws UnsupportedOperationException if called on a non-automotive device.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE)
+ public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference,
+ @Nullable @CallbackExecutor final Executor executor,
+ @Nullable final OnSetOemNetworkPreferenceListener listener) {
+ Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+ if (null != listener) {
+ Objects.requireNonNull(executor, "Executor must be non-null");
+ }
+ final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null :
+ new IOnSetOemNetworkPreferenceListener.Stub() {
+ @Override
+ public void onComplete() {
+ executor.execute(listener::onComplete);
+ }
+ };
+
+ try {
+ mService.setOemNetworkPreference(preference, listenerInternal);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index f909d13..6391802 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -20,6 +20,7 @@
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager;
import android.net.IConnectivityDiagnosticsCallback;
+import android.net.IOnSetOemNetworkPreferenceListener;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.LinkProperties;
@@ -42,9 +43,6 @@
import android.os.ResultReceiver;
import com.android.connectivity.aidl.INetworkAgent;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
/**
* Interface that answers queries about, and allows changing, the
@@ -122,35 +120,8 @@
ProxyInfo getProxyForNetwork(in Network nework);
- boolean prepareVpn(String oldPackage, String newPackage, int userId);
-
- void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
-
- ParcelFileDescriptor establishVpn(in VpnConfig config);
-
- boolean provisionVpnProfile(in VpnProfile profile, String packageName);
-
- void deleteVpnProfile(String packageName);
-
- void startVpnProfile(String packageName);
-
- void stopVpnProfile(String packageName);
-
- VpnConfig getVpnConfig(int userId);
-
- @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- void startLegacyVpn(in VpnProfile profile);
-
- LegacyVpnInfo getLegacyVpnInfo(int userId);
-
- boolean updateLockdownVpn();
- boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
- boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
- in List<String> lockdownWhitelist);
- String getAlwaysOnVpnPackage(int userId);
- boolean isVpnLockdownEnabled(int userId);
- List<String> getVpnLockdownWhitelist(int userId);
void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
+ void setLegacyLockdownVpnEnabled(boolean enabled);
void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
@@ -199,10 +170,6 @@
int getRestoreDefaultNetworkDelay(int networkType);
- boolean addVpnAddress(String address, int prefixLength);
- boolean removeVpnAddress(String address, int prefixLength);
- boolean setUnderlyingNetworksForVpn(in Network[] networks);
-
void factoryReset();
void startNattKeepalive(in Network network, int intervalSeconds,
@@ -222,8 +189,6 @@
byte[] getNetworkWatchlistConfigHash();
int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
- boolean isCallerCurrentAlwaysOnVpnApp();
- boolean isCallerCurrentAlwaysOnVpnLockdownApp();
void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
in NetworkRequest request, String callingPackageName);
@@ -245,5 +210,6 @@
void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
void unregisterQosCallback(in IQosCallback callback);
- void setOemNetworkPreference(in OemNetworkPreferences preference);
+ void setOemNetworkPreference(in OemNetworkPreferences preference,
+ in IOnSetOemNetworkPreferenceListener listener);
}
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/core/java/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
similarity index 88%
rename from core/java/android/net/VpnTransportInfo.java
rename to packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 082fa58..0242ba0 100644
--- a/core/java/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -16,8 +16,10 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -26,7 +28,15 @@
import java.util.Objects;
-/** @hide */
+/**
+ * Container for VPN-specific transport information.
+ *
+ * @see android.net.TransportInfo
+ * @see NetworkCapabilities#getTransportInfo()
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
public final class VpnTransportInfo implements TransportInfo, Parcelable {
private static final SparseArray<String> sTypeToString =
MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
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/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 252670a..8e1774b 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
svetoslavganov@google.com
-moltmann@google.com
toddke@google.com
suprabh@google.com
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
new file mode 100644
index 0000000..46e2d45
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
new file mode 100644
index 0000000..d9cd590
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
new file mode 100644
index 0000000..e80fd08
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
new file mode 100644
index 0000000..493912b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
new file mode 100644
index 0000000..af677fb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
new file mode 100644
index 0000000..68b39da
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 889980a..577fb66 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string>
<string name="user_nickname" msgid="262624187455825083">"Bynaam"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Beëindig gastesessie"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gas"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data, drie stawe."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasein vol."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet is ontkoppel."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet gekoppel."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index c41e4d5..c554e2e 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string>
<string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"የእንግዳ ክፍለ-ጊዜ ጨርስ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"የውሂብ ሦስት አሞሌዎች።"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"የውሂብ አመልካች ሙሉ ነው።"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ኤተርኔት ተነቅሏል።"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ኤተርኔት ተገናኝቷል።"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ኢተርኔት።"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index f8d1d57..b23cebd 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -558,7 +558,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string>
<string name="user_nickname" msgid="262624187455825083">"اللقب"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"إنهاء جلسة الضيف"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string>
@@ -595,5 +595,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"إشارة البيانات تتكون من ثلاثة أشرطة."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"إشارة البيانات كاملة."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"تم قطع اتصال Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"تم إنشاء اتصال Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"إيثرنت"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c6078f8..2380b2f 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string>
<string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ডেটা ছিংগনেলত তিনিডাল দণ্ড আছে।"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ডেটা ছিগনেল পূৰা আছে।"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথাৰনেট সংযোগ বিচ্ছিন্ন হৈছে।"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথাৰনেট সংযোগ হৈছে।"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথাৰনেট।"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index d3e0a25..f70bb84 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string>
<string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Qonaq sessiyasını bitirin"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data üç xətdir."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data siqnalı tamdır."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kəsilib."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet qoşuludur."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 8541222..749f864 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal za podatke od tri crte."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za podatke je najjači."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa eternetom je prekinuta."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Eternet je povezan."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index aab600f..6b2740d 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string>
<string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Завяршыць гасцявы сеанс"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Госць"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"3 планкі дадзеных."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Поўны сігнал перадачы дадзеных."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet адлучаны."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet падлучаны."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 92f2935..1a24b9e 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string>
<string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Данните са с три чертички."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналът за данни е пълен."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Връзката с Ethernet е прекратена."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Установена е връзка с Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index b40e24e..1c9f84f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string>
<string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"গেস্ট সেশন শেষ করুন"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"তিন দন্ড ডেটার সংকেত৷"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"পূর্ণ ডেটার সংকেত রয়েছে৷"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথারনেটের সংযোগ বিচ্ছিন্ন হয়েছে৷"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথারনেট সংযুক্ত হয়েছে৷"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথারনেট।"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cec2e45..f701421 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Prijenos podataka na tri crtice."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za prijenos podataka pun."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa Ethernetom je prekinuta."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet je spojen."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 6134da8..78ef109 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string>
<string name="user_nickname" msgid="262624187455825083">"Àlies"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Finalitza la sessió de convidat"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Senyal de dades: tres barres."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Senyal de dades: complet."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"S\'ha desconnectat l\'Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connectada"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index f29a3dd..8ca9800 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string>
<string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončení relace hosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Host"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tři čárky signálu datové sítě."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál datové sítě."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Síť ethernet je odpojena."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Síť ethernet je připojena."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d92d41d..5d55a12 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
<string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Afslut gæstesessionen"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tre bjælker."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal fuldt."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er ikke tilsluttet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilsluttet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index ac05b620..1fbb391 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string>
<string name="user_nickname" msgid="262624187455825083">"Alias"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsitzung beenden"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datensignal - drei Balken"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Volle Datensignalstärke"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet nicht verbunden"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbunden"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 0b11d69..32bcf87 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string>
<string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Λήξη περιόδου σύνδεσης επισκέπτη"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Τρεις γραμμές δεδομένων."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Πλήρες σήμα δεδομένων."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Το Ethernet αποσυνδέθηκε."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Το Ethernet συνδέθηκε."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index aa0d3f1..198fee4 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index c01f3a0..27cd8b3 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"End guest session"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 1da8e37..0f76576 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
<string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos completa"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 54226ce..abd209c 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
<string name="user_nickname" msgid="262624187455825083">"Apodo"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos al máximo"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Conexión Ethernet desconectada."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conexión Ethernet conectada."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 4c747e3..2e9f3b8 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string>
<string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Lõpeta külastajaseanss"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Andmeside: kolm pulka."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Andmesignaal on tugev."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Etherneti-ühendus on katkestatud."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Etherneti-ühendus on loodud."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 6016cd2..ecb4f75 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string>
<string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datu-seinaleak hiru barra ditu."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datu-seinale osoa."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bidezko konexioa eten da."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bidez konektatu da."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index e855570..56ade54 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string>
<string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"پایان دادن به جلسه مهمان"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
<string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"سه نوار برای داده."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"قدرت سیگنال داده کامل است."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"اترنت قطع شد."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"اترنت متصل شد."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"اترنت."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 724e52a..e26ecb7 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string>
<string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Lopeta Vierailija-käyttökerta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datasignaali - kolme palkkia"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Vahva kuuluvuus."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet on irrotettu."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet on yhdistetty."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 74c3005..d61ebc0 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index c9651ce..64c06fa 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Fermer la session Invité"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f499f58..cf63dd7 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string>
<string name="user_nickname" msgid="262624187455825083">"Alcume"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de sinal de datos"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de datos: completo"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Desconectouse a Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conectouse a Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 23ee1de..427ce56 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string>
<string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"અતિથિ સત્ર સમાપ્ત કરો"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
<string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ડેટા ત્રણ બાર."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ડેટા સિગ્નલ પૂર્ણ."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ઇથરનેટ ડિસ્કનેક્ટ થયું."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ઇથરનેટ કનેક્ટ થયું."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ઇથરનેટ."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index a2fc43c..159d2db 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string>
<string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
<string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूरा."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ईथरनेट डिस्कनेक्ट किया गया."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ईथरनेट कनेक्ट किया गया."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ईथरनेट."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 396051d..5e33ef3 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Završi gostujuću sesiju"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatkovni signal tri stupca."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal pun."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Prekinuta je veza s ethernetom."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Uspostavljena je veza s ethernetom."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 0c9bc54..28975be 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string>
<string name="user_nickname" msgid="262624187455825083">"Becenév"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"A vendég munkamenet befejezése"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Adat három sáv."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Adatjel teljes."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet leválasztva."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet csatlakoztatva."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 56c93b6..a207526 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string>
<string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Տվյալների երեք գիծ:"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Տվյալների ազդանշանը լրիվ է:"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet-ը անջատված է:"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet-ը կապակցված է:"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet։"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 0440f47..c4d752d 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string>
<string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Akhiri sesi tamu"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga batang."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinyal data penuh."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet terputus."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tersambung."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 0bb294f..56b0a81 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string>
<string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Ljúka gestalotu"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sendistyrkur gagnatengingar er þrjú strik."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Fullur sendistyrkur gagnatengingar."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet aftengt."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tengt."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 54049d7..f045d27 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Termina sessione Ospite"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string>
@@ -577,7 +577,7 @@
<string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string>
<string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
<string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
- <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"Wi-Fi operatore"</string>
+ <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
<string name="cell_data_off_content_description" msgid="2280700839891636498">"Dati mobili disattivati"</string>
<string name="not_default_data_content_description" msgid="6517068332106592887">"Non impostato per l\'utilizzo dei dati"</string>
<string name="accessibility_no_phone" msgid="2687419663127582503">"Nessun telefono."</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: tre barre."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Massimo segnale dati."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Connessione Ethernet annullata."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Connessione Ethernet stabilita."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 6cc4347..ba3a467 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string>
<string name="user_nickname" msgid="262624187455825083">"כינוי"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"הפסקת הגלישה כאורח"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
<string name="guest_nickname" msgid="6332276931583337261">"אורח"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"שלושה פסים של נתונים."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"אות הנתונים מלא."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"אתרנט מנותק."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"אתרנט מחובר."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"אתרנט."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index ec674ad..6f398b6 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string>
<string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"データ信号:レベル3"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"データ信号:フル"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"イーサネット接続を解除しました。"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"イーサネットに接続しました。"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"イーサネット。"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 30ab3b4..5bf1dd0 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
<string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"სტუმრის სესიის დასრულება"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
<string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"მონაცემების გადაცემა: სამი ზოლი"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"მონაცემთა გადაცემის საიმედო სიგნალი."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet კავშირი შეწყვეტილია."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet დაკავშირებულია."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index ef78527..c7c3837 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
<string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Қонақ сеансын аяқтау"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дерекқор үш баған."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дерекқор сигналы толы."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажыратылған."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet қосылған."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index ed59c74..99a73e1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើតអ្នកប្រើប្រាស់ថ្មី…"</string>
<string name="user_nickname" msgid="262624187455825083">"ឈ្មោះហៅក្រៅ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូលភ្ញៀវ"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"បញ្ចប់វគ្គភ្ញៀវ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"លុបភ្ញៀវ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើសរូបភាព"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ទិន្នន័យបីកាំ។"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"សញ្ញាទិន្នន័យពេញ។"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"បានផ្តាច់អ៊ីសឺរណិត។"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"បានភ្ជាប់អ៊ីសឺរណិត។"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"អ៊ីសឺរណិត។"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 995dc86..54a42a8 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸಿ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ಡೇಟಾ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ಡೇಟಾ ಸಂಕೇತ ತುಂಬಿದೆ."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ಇಥರ್ನೆಟ್."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 7863d48..9e9fea9 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string>
<string name="user_nickname" msgid="262624187455825083">"닉네임"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"게스트 세션 종료"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
<string name="guest_nickname" msgid="6332276931583337261">"게스트"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"데이터 신호 막대가 세 개입니다."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"데이터 신호가 강합니다."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"이더넷에서 연결 해제되었습니다."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"이더넷에 연결되었습니다."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"이더넷에 연결되었습니다."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 6af32cc..7c0fbae 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -86,7 +86,7 @@
<item msgid="8147982633566548515">"карта14"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="2494959071796102843">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="4055460186095649420">"SBC"</item>
<item msgid="720249083677397051">"AAC"</item>
<item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -94,7 +94,7 @@
<item msgid="3825367753087348007">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="8868109554557331312">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="9024885861221697796">"SBC"</item>
<item msgid="4688890470703790013">"AAC"</item>
<item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -102,38 +102,38 @@
<item msgid="2553206901068987657">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="926809261293414607">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="8003118270854840095">"44,1 кГц"</item>
<item msgid="3208896645474529394">"48,0 кГц"</item>
<item msgid="8420261949134022577">"88,2 кГц"</item>
<item msgid="8887519571067543785">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="2284090879080331090">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="2284090879080331090">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="1872276250541651186">"44,1 кГц"</item>
<item msgid="8736780630001704004">"48,0 кГц"</item>
<item msgid="7698585706868856888">"88,2 кГц"</item>
<item msgid="8946330945963372966">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="2574107108483219051">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="2574107108483219051">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="4671992321419011165">"16 бит/үлгү"</item>
<item msgid="1933898806184763940">"24 бит/үлгү"</item>
<item msgid="1212577207279552119">"32 бит/үлгү"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="9196208128729063711">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="9196208128729063711">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="1084497364516370912">"16 бит/үлгү"</item>
<item msgid="2077889391457961734">"24 бит/үлгү"</item>
<item msgid="3836844909491316925">"32 бит/үлгү"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="3014194562841654656">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="3014194562841654656">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="5982952342181788248">"Моно"</item>
<item msgid="927546067692441494">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="1997302811102880485">"Тутум тандаганды колдонуу (демейки)"</item>
+ <item msgid="1997302811102880485">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="8005696114958453588">"Моно"</item>
<item msgid="1333279807604675720">"Стерео"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 45d05c6..2aec5cb 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -143,7 +143,7 @@
<string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
<string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Алынып салынган колдонмолор"</string>
<string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Өчүрүлгөн колдонмолор жана колдонуучулар"</string>
- <string name="data_usage_ota" msgid="7984667793701597001">"Тутум жаңыртуулары"</string>
+ <string name="data_usage_ota" msgid="7984667793701597001">"Системанын жаңыртуулары"</string>
<string name="tether_settings_title_usb" msgid="3728686573430917722">"USB модем"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"Wi-Fi байланыш түйүнү"</string>
<string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth модем"</string>
@@ -162,7 +162,7 @@
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Негизги тон"</string>
<string name="tts_default_pitch_summary" msgid="9132719475281551884">"Синтезделген кептин интонациясына таасирин тийгизет"</string>
<string name="tts_default_lang_title" msgid="4698933575028098940">"Тил"</string>
- <string name="tts_lang_use_system" msgid="6312945299804012406">"Тутум тилин колдонуу"</string>
+ <string name="tts_lang_use_system" msgid="6312945299804012406">"Системанын тилин колдонуу"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Тил тандалган жок"</string>
<string name="tts_default_lang_summary" msgid="9042620014800063470">"Текстти окуй турган тилди тандоо"</string>
<string name="tts_play_example_title" msgid="1599468547216481684">"Үлгүнү угуу"</string>
@@ -483,7 +483,7 @@
<string name="retail_demo_reset_next" msgid="3688129033843885362">"Кийинки"</string>
<string name="retail_demo_reset_title" msgid="1866911701095959800">"Сырсөз талап кылынат"</string>
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Жигердүү киргизүү ыкмалары"</string>
- <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Тутум тилдерин колдонуу"</string>
+ <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Системанын тилдерин колдонуу"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> тууралоолору ачылган жок"</string>
<string name="ime_security_warning" msgid="6547562217880551450">"Бул киргизүү ыкмасы сиз терген бардык тексттер, сырсөздөр жана кредиттик карталар сыяктуу жеке маалыматтарды кошо чогултушу мүмкүн. Бул <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> колдонмосу менен байланыштуу. Ушул киргизүү ыкма колдонулсунбу?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Эскертүү: Өчүрүп-күйгүзгөндөн кийин, бул колдонмо телефондун кулпусу ачылмайынча иштебейт"</string>
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string>
<string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Конок сеансын бүтүрүү"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Конок"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Мобилдик интернеттин сигналы үч таякча."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Мобилдик интернеттин сигналы толук."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажырады."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet туташты."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index ca6e06c..e199e1f 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string>
<string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ສິ້ນສຸດເຊດຊັນແຂກ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ຂໍ້ມູນສາມຂີດ."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ສັນຍານຂໍ້ມູນເຕັມ."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ອີເທີເນັດຕັດເຊື່ອມຕໍ່ແລ້ວ."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ອີເທີເນັດເຊື່ອມຕໍ່ແລ້ວ."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ອີເທີເນັດ."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 173b57e..a25c59f5 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string>
<string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Baigti svečio sesiją"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Trys duomenų juostos."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Stiprus duomenų signalas."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Atsijungta nuo eterneto."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Prijungta prie eterneto."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternetas."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index a8bd2cc..f360c90 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string>
<string name="user_nickname" msgid="262624187455825083">"Segvārds"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Beigt viesa sesiju"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: trīs joslas."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Pilna piekļuve datu signālam."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Pārtraukts savienojums ar tīklu Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Izveidots savienojums ar tīklu Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Tīkls Ethernet"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0ef3f0e..a32d00b 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string>
<string name="user_nickname" msgid="262624187455825083">"Прекар"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши ја гостинската сесија"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Податоци три цртички."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналот за податоци е исполнет."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Етернетот е исклучен."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернетот е поврзан."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e6289ae..902507d 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു…"</string>
<string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"അതിഥി സെഷൻ അവസാനിപ്പിക്കുക"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
<string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ഡാറ്റ മൂന്ന് ബാർ."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ഡാറ്റ സിഗ്നൽ പൂർണ്ണമാണ്."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ഇതർനെറ്റ് വിച്ഛേദിച്ചു."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ഇതർനെറ്റ് കണക്റ്റുചെയ്തു."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ഇതർനെറ്റ്."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ead712c..83009b7 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string>
<string name="user_nickname" msgid="262624187455825083">"Хоч"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дата гурван баганатай."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дата дохио дүүрэн."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet саллаа."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet холбогдсон."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Этернэт."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index edcc71b..359f525 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string>
<string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथी सत्र संपवा"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूर्ण."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट डिस्कनेक्ट केले."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट कनेक्ट केले."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 8a14437..eba89da 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga bar."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Isyarat data penuh."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet diputuskan sambungan."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet disambungkan."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 377c8b2..e982aa9 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string>
<string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ဧည့်သည်ဆက်ရှင်ကို အဆုံးသတ်ရန်"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ဒေတာသုံးဘား။"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ဒေတာထုတ်လွှင့်မှုအပြည့်ဖမ်းမိခြင်း"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet နှင့်ချိတ်ဆက်မှုပြတ်တောက်"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ချိတ်ဆက်ထား။"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"အီသာနက်။"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index f0e773b..abbb5e7 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string>
<string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Avslutt gjesteøkten"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data – tre stolper."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal er fullt."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er frakoblet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilkoblet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 0bde70f..d7a32bf 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string>
<string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथिको सत्र अन्त्य गर्नुहोस्"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तिन बाधाहरू।"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा संकेत पूर्ण।"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट विच्छेद भयो।"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट जोडियो।"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट।"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2bc1e8c..7e739bc 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string>
<string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Gegevens: drie streepjes."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Gegevenssignaal is op volle sterkte."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetverbinding verbroken."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbonden."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 787c871..1e842b7 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string>
<string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ଅତିଥି ସେସନ୍ ଶେଷ କରନ୍ତୁ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ଡାଟାର ତିନୋଟି ବାର୍ ଅଛି।"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ଡାଟା ସିଗ୍ନାଲ୍ ପୂର୍ଣ୍ଣ ଅଛି।"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ଇଥରନେଟ୍ ବିଚ୍ଛିନ୍ନ ହୋଇଛି।"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ଇଥରନେଟ୍ ସଂଯୁକ୍ତ ହୋଇଛି।"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9483e06..d035fc0 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਸਮਾਪਤ ਕਰੋ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">" ਡਾਟਾ ਤਿੰਨ ਬਾਰ।"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">" ਡਾਟਾ ਸਿਗਨਲ ਪੂਰਾ।"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ਈਥਰਨੈੱਟ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ਈਥਰਨੈੱਟ ਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ਈਥਰਨੈੱਟ।"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index c7f1595..8a732cb 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Kończenie sesji gościa"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gość"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dane: trzy paski."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Dane: pełna moc sygnału."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Rozłączono z siecią Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Połączono z siecią Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
<string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 94cad918..ab23059 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Terminar a sessão de convidado"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras de dados."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados completo."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desligada."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ligada."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
<string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 63d8b8f..98edb32 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Încheiați sesiunea pentru invitați"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Semnal pentru date: trei bare."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Semnal pentru date: complet."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet deconectat."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectat."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index ac43e49..972dc20 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
<string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Завершить гостевой сеанс"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал передачи данных: три деления."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Надежный сигнал передачи данных."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Устройство отключено от Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Устройство подключено к Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 08fd9a0..0dee8e1 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string>
<string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"ආරාධිත සැසිය අවසන් කරන්න"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
<string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"දත්ත තීරු 3."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"දත්ත සංඥාව පිරී ඇත."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ඊතර්නෙට් විසන්ධි කරන ලදී."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ඊතර්නෙට් සම්බන්ධ කරන ලදී."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ඊතර්නෙට්."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 8f05684..7d49ca3 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string>
<string name="user_nickname" msgid="262624187455825083">"Prezývka"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončiť reláciu hosťa"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tri čiarky signálu dátovej siete."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál dátovej siete."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Sieť ethernet je odpojená"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Sieť ethernet je pripojená"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 3dccf3b..48e5dcc 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string>
<string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Končaj sejo gosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatki s tremi črticami."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal poln."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetna povezava je prekinjena."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernetna povezava je vzpostavljena."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c09ad4c..0bc905e 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
<string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sinjali është me tre vija."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinjali i të dhënave është i plotë."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Lidhja e eternetit u shkëput."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Lidhja e eternetit u lidh."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b5a91bf..6d19af8 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -555,7 +555,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string>
<string name="user_nickname" msgid="262624187455825083">"Надимак"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши сесију госта"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string>
@@ -592,5 +592,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал за податке од три црте."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигнал за податке је најјачи."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Веза са етернетом је прекинута."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернет је повезан."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 16a1be6..944c423 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string>
<string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Avsluta gästsession"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data: tre staplar."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignalen är full."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet har kopplats från."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet har anslutits."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 58896c8..442f54b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
<string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Maliza kipindi cha mgeni"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Fito tatu za habari."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Ishara ya data imejaa."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethaneti imeondolewa."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethaneti imeunganishwa."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethaneti."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 71cf7d4..561a122 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string>
<string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"விருந்தினர் அமர்வை நிறைவுசெய்"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
<string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"தரவு சிக்னல் மூன்று கோட்டில் உள்ளது."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"தரவு சிக்னல் முழுமையாக உள்ளது."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ஈத்தர்நெட் துண்டிக்கப்பட்டது."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ஈத்தர்நெட் இணைக்கப்பட்டது."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ஈதர்நெட்."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c3a65e7..18d7f28 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string>
<string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"గెస్ట్ సెషన్ను ముగించు"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
<string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్ను ఎంచుకోండి"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"డేటా మూడు బార్లు."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"డేటా సిగ్నల్ సంపూర్ణంగా ఉంది."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ఈథర్నెట్ డిస్కనెక్ట్ చేయబడింది."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ఈథర్నెట్ కనెక్ట్ చేయబడింది."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ఈథర్నెట్."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 903accb..ceab26c 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
<string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"สัญญาณข้อมูลสามขีด"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"สัญญาณข้อมูลเต็ม"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ยกเลิกการเชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"เชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"อีเทอร์เน็ต"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index b259201..671fa14 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data na tatlong bar."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Puno ang signal ng data."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Nadiskonekta ang Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Nakakonekta ang Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index a0bc362..98d89f3 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string>
<string name="user_nickname" msgid="262624187455825083">"Takma ad"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Veri sinyali üç çubuk."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Veri sinyali tam."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kesildi."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bağlandı."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 509c8a6..4e739ac 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string>
<string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Завершити сеанс у режимі \"Гість\""</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гість"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string>
@@ -593,5 +593,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Три смужки сигналу даних."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Максимальний сигнал даних."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet відключено."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet підключено."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e097ab2..c7dee95 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string>
<string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"مہمان سیشن ختم کریں"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
<string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"ڈیٹا کے تین بارز۔"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"ڈیٹا سگنل بھرا ہوا ہے۔"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ایتھرنیٹ منقطع ہے۔"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ایتھرنیٹ منسلک ہے۔"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ایتھرنیٹ۔"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 8c2a95d..433096b 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nik"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Ma’lumotlar uchta panelda."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Internet signali butun."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Qurilma Ethernet tarmog‘idan uzildi."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Qurilma Ethernet tarmog‘iga ulandi."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7a4d89b..a14462b 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string>
<string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Khách"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tín hiệu dữ liệu ba vạch."</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Tín hiệu dữ liệu đầy đủ."</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Đã ngắt kết nối Ethernet."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Đã kết nối Ethernet."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7cd61fc..f6bea91 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string>
<string name="user_nickname" msgid="262624187455825083">"昵称"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"结束访客会话"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"访客"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"数据信号强度为三格。"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"数据信号满格。"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太网已断开连接。"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"以太网已连接。"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太网。"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 458071c..3cf15a9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
<string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網絡訊號強度為三格。"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網絡訊號滿格。"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太網連接中斷。"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連接以太網。"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太網絡。"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 867ed8b..7cf0297 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
<string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網路訊號強度三格。"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網路訊號滿格。"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"未連上乙太網路。"</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連上乙太網路。"</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"乙太網路。"</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e64dbd3..d950d58 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -554,7 +554,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string>
<string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
- <string name="guest_exit_guest" msgid="4754204715192830850">"Misa isikhathi sesihambeli"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string>
@@ -591,5 +591,7 @@
<string name="accessibility_data_three_bars" msgid="2813876214466722413">"Amabha amathathu edatha"</string>
<string name="accessibility_data_signal_full" msgid="1808301899314382337">"Igcwele i-signal yedatha"</string>
<string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"I-Ethernet inqanyuliwe."</string>
- <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"I-Ethernet ixhunyiwe."</string>
+ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"I-Ethernet."</string>
+ <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7556ace..3866151 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1380,7 +1380,7 @@
<!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
<string name="guest_new_guest">Add guest</string>
<!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
- <string name="guest_exit_guest">End guest session</string>
+ <string name="guest_exit_guest">Remove guest</string>
<!-- Name for the guest user [CHAR LIMIT=35] -->
<string name="guest_nickname">Guest</string>
@@ -1485,4 +1485,7 @@
<string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
<!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_ethernet_connected">Ethernet.</string>
+
+ <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_no_calling">No calling.</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index 45028ff..eff9e74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -48,6 +48,8 @@
public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+ public static final int NO_CALLING = R.string.accessibility_no_calling;
+
public static final int[] ETHERNET_CONNECTION_VALUES = {
R.string.accessibility_ethernet_disconnected,
R.string.accessibility_ethernet_connected,
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/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 09f285b..14a7cfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -65,7 +65,7 @@
return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
- case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
+ case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
default:
return "unsupported";
@@ -180,7 +180,7 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
TelephonyIcons.NR_5G);
networkToIconLookup.put(toDisplayIconKey(
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
TelephonyIcons.NR_5G_PLUS);
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_NR),
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 4c7b898..0cd5e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -266,7 +266,7 @@
serviceState.getDataRegState()) + ")")
.append(',')
.append("signalStrength=").append(signalStrength == null ? ""
- : signalStrength.toString()).append(',')
+ : signalStrength.getLevel()).append(',')
.append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? ""
: telephonyDisplayInfo.toString()).append(']').toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 0cb9906..e3413aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -317,5 +317,21 @@
ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
}
+
+ public static final int[] WIFI_CALL_STRENGTH_ICONS = {
+ R.drawable.ic_wifi_call_strength_1,
+ R.drawable.ic_wifi_call_strength_1,
+ R.drawable.ic_wifi_call_strength_2,
+ R.drawable.ic_wifi_call_strength_3,
+ R.drawable.ic_wifi_call_strength_3
+ };
+
+ public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
+ R.drawable.ic_mobile_call_strength_1,
+ R.drawable.ic_mobile_call_strength_1,
+ R.drawable.ic_mobile_call_strength_2,
+ R.drawable.ic_mobile_call_strength_3,
+ R.drawable.ic_mobile_call_strength_3
+ };
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 78282fb..841a49e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -39,6 +39,8 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -47,6 +49,8 @@
* Track status of Wi-Fi for the Sys UI.
*/
public class WifiStatusTracker {
+ private static final int HISTORY_SIZE = 32;
+ private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private final Context mContext;
private final WifiNetworkScoreCache mWifiNetworkScoreCache;
private final WifiManager mWifiManager;
@@ -54,6 +58,10 @@
private final ConnectivityManager mConnectivityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Set<Integer> mNetworks = new HashSet<>();
+ // Save the previous HISTORY_SIZE states for logging.
+ private final String[] mHistory = new String[HISTORY_SIZE];
+ // Where to copy the next state into.
+ private int mHistoryIndex;
private final WifiNetworkScoreCache.CacheListener mCacheListener =
new WifiNetworkScoreCache.CacheListener(mHandler) {
@Override
@@ -93,6 +101,13 @@
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
}
+ String log = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append("onCapabilitiesChanged: ")
+ .append("network=").append(network).append(",")
+ .append("networkCapabilities=").append(networkCapabilities)
+ .toString();
+ recordLastWifiNetwork(log);
if (wifiInfo != null) {
updateWifiInfo(wifiInfo);
updateStatusLabel();
@@ -102,6 +117,12 @@
@Override
public void onLost(Network network) {
+ String log = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append("onLost: ")
+ .append("network=").append(network)
+ .toString();
+ recordLastWifiNetwork(log);
if (mNetworks.contains(network.getNetId())) {
mNetworks.remove(network.getNetId());
updateWifiInfo(null);
@@ -336,4 +357,25 @@
}
return null;
}
+
+ private void recordLastWifiNetwork(String log) {
+ mHistory[mHistoryIndex] = log;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
+ }
+
+ /** Dump function. */
+ public void dump(PrintWriter pw) {
+ pw.println(" - WiFi Network History ------");
+ int size = 0;
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ if (mHistory[i] != null) size++;
+ }
+ // Print out the previous states in ordered number.
+ for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+ i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+ pw.println(" Previous WiFiNetwork("
+ + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+ + mHistory[i & (HISTORY_SIZE - 1)]);
+ }
+ }
}
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 1432544..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" />
@@ -400,6 +401,10 @@
<uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+ <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
+
+ <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+ <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 6ba1fcb..34901f5 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@
svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
-moltmann@google.com
toddke@google.com
cbrubaker@google.com
omakoto@google.com
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/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
index 0831e0e..da079cf0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -103,5 +103,10 @@
default Animator getOutAnimation() {
return null;
}
+
+ /**
+ * Called on orientation changes.
+ */
+ default void onOrientationChange(int orientation) { }
}
}
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/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
new file mode 100644
index 0000000..174981e
--- /dev/null
+++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_activated="true"
+ android:color="@color/kg_user_switcher_avatar_background" />
+ <item android:color="@color/kg_user_switcher_avatar_background" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml
new file mode 100644
index 0000000..5644b65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml
@@ -0,0 +1,25 @@
+<?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
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:width="@dimen/end_guest_button_border_size"
+ android:color="?android:attr/colorControlHighlight" />
+ <corners android:radius="@dimen/end_guest_button_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
new file mode 100644
index 0000000..addb3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="100"
+ android:viewportHeight="100">
+
+ <path
+ android:fillColor="@color/kg_user_switcher_avatar_background"
+ android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml
new file mode 100644
index 0000000..5c45e83
--- /dev/null
+++ b/packages/SystemUI/res/drawable/toast_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#FFFFFFFF" />
+ <corners android:radius="@dimen/toast_bg_radius" />
+</shape>
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/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 416ee81..2789ed1 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -43,17 +43,12 @@
<include layout="@layout/system_icons" />
</FrameLayout>
- <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
- android:layout_width="@dimen/multi_user_switch_width_keyguard"
- android:layout_height="match_parent"
- android:background="@drawable/ripple_drawable"
- android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin">
- <ImageView android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:layout_gravity="center"
- android:scaleType="centerInside"/>
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <ImageView android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
</LinearLayout>
<Space
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 983ba6d..253c03e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,10 +14,50 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container"
- android:visibility="gone"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here -->
-</view>
\ No newline at end of file
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_user_switcher_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+
+ <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
+ android:id="@+id/keyguard_user_switcher_list"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="top|end"
+ android:gravity="end" />
+
+ <LinearLayout
+ android:id="@+id/end_guest_button"
+ android:layout_height="@dimen/end_guest_button_layout_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="@dimen/end_guest_button_margin_bottom"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:paddingLeft="@dimen/end_guest_button_padding_horizontal"
+ android:paddingRight="@dimen/end_guest_button_padding_horizontal"
+ android:background="@drawable/end_guest_button_background"
+ android:visibility="gone">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:src="@drawable/ic_exit_to_app"
+ android:background="@android:color/transparent"
+ android:color="?attr/wallpaperTextColor" />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:gravity="center"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textColor="?attr/wallpaperTextColor"
+ android:textSize="13sp"
+ android:text="@string/guest_exit_button" />
+ </LinearLayout>
+
+</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
deleted file mode 100644
index 4c1042e..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2016 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/keyguard_user_switcher_inner"
- android:orientation="vertical"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
- android:layout_gravity="end"
- android:gravity="end"
- android:paddingTop="4dp">
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 1cd1a04..aaa372a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -19,29 +19,30 @@
<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_marginEnd="8dp"
- android:gravity="center_vertical"
+ android:gravity="end|center_vertical"
android:clickable="true"
- android:background="@drawable/ripple_drawable"
- sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
- sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
- <TextView android:id="@+id/user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="13dp"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
- />
- <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:contentDescription="@null"
- sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
- sysui:framePadding="2.5dp"
- sysui:badgeDiameter="18dp"
- sysui:badgeMargin="1dp"
- sysui:frameColor="@color/kg_user_switcher_rounded_background_color" />
+ android:background="@drawable/kg_user_switcher_rounded_bg"
+ systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+ systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
+ <TextView
+ android:id="@+id/user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="16dp" />
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/user_picture"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameWidth="0dp"
+ systemui:framePadding="0dp"
+ systemui:frameColor="@color/kg_user_avatar_frame" />
</com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 8a47a22..95cee66 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -47,7 +47,7 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="4dp"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:forceHasOverlappingRendering="false"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 6b42705..a4cf5ed 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -48,7 +48,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:gravity="start"
android:textSize="14sp" />
@@ -58,7 +58,7 @@
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:gravity="end"
android:textSize="14sp" />
</FrameLayout>
@@ -120,13 +120,13 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center_vertical|end"
+ android:gravity="center"
+ android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:foreground="@drawable/qs_media_seamless_background"
- android:background="@drawable/qs_media_light_source"
+ android:background="@drawable/qs_media_seamless_background"
android:orientation="horizontal"
android:padding="6dp"
android:contentDescription="@string/quick_settings_media_device_label">
@@ -135,7 +135,7 @@
android:layout_width="@dimen/qs_seamless_icon_size"
android:layout_height="@dimen/qs_seamless_icon_size"
android:layout_gravity="center"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/colorPrimary"
android:src="@*android:drawable/ic_media_seamless" />
<TextView
android:visibility="gone"
@@ -147,7 +147,7 @@
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:text="@*android:string/ext_media_seamless_action"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/colorPrimary"
android:textDirection="locale"
android:textSize="14sp" />
</LinearLayout>
@@ -157,7 +157,7 @@
android:id="@+id/media_seamless_fallback"
android:layout_width="@dimen/qs_seamless_icon_size"
android:layout_height="@dimen/qs_seamless_icon_size"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_cast_connected"
android:forceHasOverlappingRendering="false" />
@@ -171,15 +171,15 @@
android:clickable="true"
android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding"
- android:thumbTint="@color/media_primary_text"
- android:progressTint="@color/media_seekbar_progress"
- android:progressBackgroundTint="@color/media_disabled"
+ android:thumbTint="?android:attr/textColorPrimary"
+ android:progressTint="?android:attr/textColorPrimary"
+ android:progressBackgroundTint="?android:attr/colorBackground"
android:splitTrack="false" />
<!-- App name -->
<TextView
android:id="@+id/app_name"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
@@ -194,7 +194,7 @@
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:singleLine="true"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<!-- Artist name -->
@@ -204,12 +204,12 @@
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
- android:textColor="@color/media_secondary_text"
+ android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="6dp" />
@@ -223,7 +223,7 @@
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
android:id="@+id/media_text"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/controls_media_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -238,7 +238,7 @@
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_close_session"
app:layout_constraintTop_toBottomOf="@id/media_text"
app:layout_constraintStart_toStartOf="parent"
@@ -262,7 +262,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_settings_button" />
</FrameLayout>
@@ -283,7 +283,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/cancel" />
</FrameLayout>
@@ -304,7 +304,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_dismiss_button"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d6385ff..a8ef7c3 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,18 @@
android:layout_height="match_parent"
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"
+ android:layout_width="match_parent" />
+
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
@@ -57,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"
@@ -72,12 +91,6 @@
<include layout="@layout/photo_preview_overlay" />
- <ViewStub
- android:id="@+id/keyguard_user_switcher"
- android:layout="@layout/keyguard_user_switcher"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..de4e062
--- /dev/null
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/toast_width"
+ android:orientation="horizontal"
+ android:background="@drawable/toast_background"
+ android:backgroundTint="?android:attr/colorBackground"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:gravity="center_vertical">
+
+ <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
+ <ImageView
+ android:id="@+id/icon"
+ android:alpha="@dimen/toast_icon_alpha"
+ android:padding="11.5dp"
+ android:layout_width="@dimen/toast_icon_size"
+ android:layout_height="@dimen/toast_icon_size"/>
+ <TextView
+ android:id="@+id/text"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="22dp"
+ android:textSize="@dimen/toast_text_size"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..24c7655 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,4 +52,6 @@
<!-- (footer_height -48dp)/2 -->
<dimen name="controls_management_footer_top_margin">4dp</dimen>
<dimen name="controls_management_favorites_top_margin">8dp</dimen>
+
+ <dimen name="toast_y_offset">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3153d0d..37ec576 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -89,6 +89,8 @@
<color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
<!-- Icon color for selected user avatars in keyguard user switcher -->
<color name="kg_user_switcher_selected_avatar_icon_color">#202124</color>
+ <!-- Color of background circle of user avatars in keyguard user switcher -->
+ <color name="kg_user_switcher_avatar_background">#3C4043</color>
<!-- Icon color for user avatars in quick settings user switcher -->
<color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color>
<!-- Icon color for selected user avatars in quick settings user switcher -->
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 02bd602..ee2b82d 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -23,13 +23,6 @@
<item name="numColumns">4</item>
</style>
- <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
- <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
- <item name="android:textStyle">normal</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">?attr/wallpaperTextColor</item>
- </style>
-
<style name="TextAppearance.QS.UserSwitcher">
<item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8166e35..a1191ae 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -139,6 +139,10 @@
<!-- Size of shadows/elevations on keyguard -->
<attr name="shadowRadius" format="float" />
+ <attr name="handleThickness" format="dimension" />
+ <attr name="handleColor" format="color" />
+ <attr name="scrimColor" format="color" />
+
<!-- Used display CarrierText in Keyguard or QS Footer -->
<declare-styleable name="CarrierText">
<attr name="allCaps" format="boolean" />
@@ -173,15 +177,15 @@
</declare-styleable>
<declare-styleable name="CropView">
- <attr name="handleThickness" format="dimension" />
- <attr name="handleColor" format="color" />
- <attr name="scrimColor" format="color" />
+ <attr name="handleThickness" />
+ <attr name="handleColor" />
+ <attr name="scrimColor" />
</declare-styleable>
<declare-styleable name="MagnifierView">
- <attr name="handleThickness" format="dimension" />
- <attr name="handleColor" format="color" />
- <attr name="scrimColor" format="color" />
+ <attr name="handleThickness" />
+ <attr name="handleColor" />
+ <attr name="scrimColor" />
<attr name="borderThickness" format="dimension" />
<attr name="borderColor" format="color" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5fb6de7..acd671c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -66,9 +66,13 @@
<!-- Color for rounded background for activated user in keyguard user switcher -->
<color name="kg_user_switcher_activated_background_color">#26000000</color>
<!-- Icon color for user avatars in keyguard user switcher -->
- <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
- <!-- Icon color for selected user avatars in keyguard user switcher -->
- <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color>
+ <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color>
+ <!-- Icon color for user avatars in keyguard user switcher that restricted
+ (e.g. cannot be switched to) -->
+ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
+ <!-- Color of background circle of user avatars in keyguard user switcher -->
+ <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color>
+
<!-- Icon color for user avatars in user switcher quick settings -->
<color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
<!-- Icon color for selected user avatars in user switcher quick settings -->
@@ -240,11 +244,8 @@
<color name="magnification_switch_button_color">#7F000000</color>
<!-- media -->
- <color name="media_primary_text">@android:color/white</color>
- <color name="media_secondary_text">#99ffffff</color> <!-- 60% -->
- <color name="media_seekbar_progress">#c0ffffff</color>
<color name="media_disabled">#80ffffff</color>
- <color name="media_seamless_border">#26ffffff</color> <!-- 15% -->
+ <color name="media_seamless_border">?android:attr/colorAccent</color>
<color name="media_divider">#1d000000</color>
<!-- controls -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb04c3b..78927f8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,7 +107,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
</string>
<!-- The tiles to display in QuickSettings -->
@@ -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 79471e6..0d92aea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -743,9 +743,6 @@
<!-- end margin for system icons if multi user switch is hidden -->
<dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
- <!-- The thickness of the colored border around the current user. -->
- <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
-
<dimen name="data_usage_graph_marker_width">4dp</dimen>
<!-- The padding bottom of the clock group when QS is expanded. -->
@@ -805,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">54dp</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>
@@ -1324,8 +1321,16 @@
<dimen name="screenrecord_status_icon_height">17.5dp</dimen>
<dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
+ <!-- Keyguard user switcher -->
<dimen name="kg_user_switcher_text_size">16sp</dimen>
+ <!-- End guest session button -->
+ <dimen name="end_guest_button_layout_height">32dp</dimen>
+ <dimen name="end_guest_button_padding_horizontal">16dp</dimen>
+ <dimen name="end_guest_button_margin_bottom">96dp</dimen>
+ <dimen name="end_guest_button_border_size">1dp</dimen>
+ <dimen name="end_guest_button_corner_radius">16dp</dimen>
+
<!-- Opacity at which the background for the shutdown UI will be drawn. -->
<item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
@@ -1354,4 +1359,11 @@
<dimen name="rounded_slider_icon_size">24dp</dimen>
<!-- rounded_slider_icon_size / 2 -->
<dimen name="rounded_slider_icon_inset">12dp</dimen>
+
+ <dimen name="toast_width">296dp</dimen>
+ <item name="toast_icon_alpha" format="float" type="dimen">1</item>
+ <dimen name="toast_text_size">14sp</dimen>
+ <dimen name="toast_y_offset">48dp</dimen>
+ <dimen name="toast_icon_size">48dp</dimen>
+ <dimen name="toast_bg_radius">28dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index ded8a2e..d4bb128 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,11 +18,12 @@
<resources>
<bool name="are_flags_overrideable">false</bool>
- <bool name="flag_notification_pipeline2">false</bool>
+ <bool name="flag_notification_pipeline2">true</bool>
<bool name="flag_notification_pipeline2_rendering">false</bool>
<bool name="flag_notif_updates">false</bool>
<bool name="flag_shade_is_opaque">false</bool>
+ <bool name="flag_monet">false</bool>
<!-- b/171917882 -->
<bool name="flag_notification_twocolumn">false</bool>
@@ -34,9 +35,11 @@
<bool name="flag_brightness_slider">false</bool>
+ <!-- People Tile flag -->
+ <bool name="flag_conversations">false</bool>
+
<!-- The new animations to/from lockscreen and AOD! -->
<bool name="flag_lockscreen_animations">false</bool>
- <!-- People Tile flag -->
- <bool name="flag_conversations">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 abcf4e8..d997ca2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1112,14 +1112,17 @@
<!-- Name for a freshly added user [CHAR LIMIT=30] -->
<string name="user_new_user_name">New user</string>
+ <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]-->
+ <string name="guest_exit_button">End guest session</string>
+
<!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
- <string name="guest_exit_guest_dialog_title">End guest session?</string>
+ <string name="guest_exit_guest_dialog_title">Remove guest?</string>
<!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
<string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
<!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
- <string name="guest_exit_guest_dialog_remove">End session</string>
+ <string name="guest_exit_guest_dialog_remove">Remove</string>
<!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
<string name="guest_wipe_session_title">Welcome back, guest!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..14b376a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -114,12 +114,12 @@
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
<item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
<item name="android:textStyle">normal</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">?attr/wallpaperTextColor</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
<item name="android:fontWeight">700</item>
- <item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance" />
@@ -582,7 +582,7 @@
<style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
<item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:tint">@android:color/white</item>
+ <item name="android:tint">?android:attr/textColorPrimary</item>
<item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
</style>
@@ -764,6 +764,7 @@
<style name="TextAppearance.PrivacyDialog">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index f834d6d..f83e3a1 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -36,25 +36,23 @@
app:layout_constraintTop_toTopOf="@id/icon"
app:layout_constraintBottom_toBottomOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
/>
<Constraint
android:id="@+id/media_seamless"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/app_name"
+ app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
- android:layout_marginEnd="@dimen/qs_center_guideline_padding"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
/>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index d89e0eb..7c67720 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -36,25 +36,23 @@
app:layout_constraintTop_toTopOf="@id/icon"
app:layout_constraintBottom_toBottomOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
/>
<Constraint
android:id="@+id/media_seamless"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/app_name"
+ app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
- android:layout_marginEnd="@dimen/qs_center_guideline_padding"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
/>
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 a5f364d..bfe7f8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,13 +16,10 @@
package com.android.keyguard;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
+import android.os.UserHandle;
import android.util.Slog;
import android.view.View;
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -50,13 +47,12 @@
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final KeyguardClockSwitchController mKeyguardClockSwitchController;
- private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final DozeParameters mDozeParameters;
+ private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
- private boolean mKeyguardStatusViewVisibilityAnimating;
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@Inject
@@ -72,16 +68,19 @@
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
mKeyguardClockSwitchController = keyguardClockSwitchController;
- mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
mNotificationIconAreaController = notificationIconAreaController;
mDozeParameters = dozeParameters;
+ mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
+ dozeParameters);
}
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
+ mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
@@ -144,7 +143,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardStatusViewVisibilityAnimating) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
mView.setAlpha(alpha);
}
}
@@ -200,7 +199,7 @@
public void updatePosition(int x, int y, float scale, boolean animate) {
// We animate the status view visible/invisible using Y translation, so don't change it
// while the animation is running.
- if (!mKeyguardStatusViewVisibilityAnimating) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
animate);
}
@@ -230,69 +229,8 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- mView.animate().cancel();
- mKeyguardStatusViewVisibilityAnimating = false;
- if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
- && statusBarState != KEYGUARD) || goingToFullShade) {
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(160)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(
- mAnimateKeyguardStatusViewGoneEndRunnable);
- if (keyguardFadingAway) {
- mView.animate()
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .start();
- }
- } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
- mView.setVisibility(View.VISIBLE);
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.setAlpha(0f);
- mView.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(320)
- .setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (statusBarState == KEYGUARD) {
- if (keyguardFadingAway) {
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0)
- .translationYBy(-getHeight() * 0.05f)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
- .setDuration(125)
- .setStartDelay(0)
- .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
- .start();
- } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
- mKeyguardStatusViewVisibilityAnimating = true;
-
- mView.setVisibility(View.VISIBLE);
- mView.setAlpha(0f);
-
- float curTranslationY = mView.getTranslationY();
- mView.setTranslationY(curTranslationY - getHeight() * 0.1f);
- mView.animate()
- .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
- .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- .translationY(curTranslationY)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
- .start();
- } else {
- mView.setVisibility(View.VISIBLE);
- mView.setAlpha(1f);
- }
- } else {
- mView.setVisibility(View.GONE);
- mView.setAlpha(1f);
- }
+ mKeyguardVisibilityHelper.setViewVisibility(
+ statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
}
private void refreshTime() {
@@ -310,6 +248,11 @@
}
}
+ private boolean shouldShowLogout() {
+ return mKeyguardUpdateMonitor.isLogoutEnabled()
+ && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ }
+
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -336,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();
}
@@ -367,7 +310,7 @@
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refreshTime();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
}
@@ -385,27 +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());
}
};
-
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- mView.setVisibility(View.INVISIBLE);
- };
-
-
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- mView.setVisibility(View.GONE);
- };
-
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
new file mode 100644
index 0000000..724e1f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -0,0 +1,137 @@
+/*
+ * 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.statusbar.StatusBarState.KEYGUARD;
+
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
+ * This logic is shared by both the keyguard status view and the keyguard user switcher.
+ */
+public class KeyguardVisibilityHelper {
+
+ private View mView;
+ private final KeyguardStateController mKeyguardStateController;
+ private final DozeParameters mDozeParameters;
+ private boolean mKeyguardViewVisibilityAnimating;
+
+ public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
+ DozeParameters dozeParameters) {
+ mView = view;
+ mKeyguardStateController = keyguardStateController;
+ mDozeParameters = dozeParameters;
+ }
+
+ public boolean isVisibilityAnimating() {
+ return mKeyguardViewVisibilityAnimating;
+ }
+
+ /**
+ * Set the visibility of a keyguard view based on some new state.
+ */
+ public void setViewVisibility(
+ int statusBarState,
+ boolean keyguardFadingAway,
+ boolean goingToFullShade,
+ int oldStatusBarState) {
+ mView.animate().cancel();
+ mKeyguardViewVisibilityAnimating = false;
+ if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+ && statusBarState != KEYGUARD) || goingToFullShade) {
+ mKeyguardViewVisibilityAnimating = true;
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ .setDuration(160)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(
+ mAnimateKeyguardStatusViewGoneEndRunnable);
+ if (keyguardFadingAway) {
+ mView.animate()
+ .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+ .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+ .start();
+ }
+ } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+ mView.setVisibility(View.VISIBLE);
+ mKeyguardViewVisibilityAnimating = true;
+ mView.setAlpha(0f);
+ mView.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setDuration(320)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+ } else if (statusBarState == KEYGUARD) {
+ if (keyguardFadingAway) {
+ mKeyguardViewVisibilityAnimating = true;
+ mView.animate()
+ .alpha(0)
+ .translationYBy(-mView.getHeight() * 0.05f)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setDuration(125)
+ .setStartDelay(0)
+ .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+ .start();
+ } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+ mKeyguardViewVisibilityAnimating = true;
+
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(0f);
+
+ float curTranslationY = mView.getTranslationY();
+ mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f);
+ mView.animate()
+ .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .alpha(1f)
+ .translationY(curTranslationY)
+ .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
+ .start();
+ } else {
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(1f);
+ }
+ } else {
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
+ }
+
+ private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.INVISIBLE);
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.GONE);
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ };
+}
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/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
similarity index 63%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
index 14d57bf..c4be1ba535 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -14,6 +14,20 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.keyguard.clock;
-parcelable ExternalTimeSuggestion;
+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/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
new file mode 100644
index 0000000..730c14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.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.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardUserSwitcher and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardUserSwitcherComponent {
+ /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ KeyguardUserSwitcherComponent build(
+ @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
+ }
+
+ /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
+ KeyguardUserSwitcherController getKeyguardUserSwitcherController();
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index 14d57bf..b9184f4 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.keyguard.dagger;
-parcelable ExternalTimeSuggestion;
+import dagger.Module;
+
+/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+@Module
+public abstract class KeyguardUserSwitcherModule {
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 14d57bf..864472e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -14,6 +14,19 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.keyguard.dagger;
-parcelable ExternalTimeSuggestion;
+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 KeyguardUserSwitcherComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardUserSwitcherScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index caaee5f..1765627 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,6 @@
import androidx.annotation.StyleRes;
-import com.android.settingslib.Utils;
import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -105,11 +104,6 @@
private DualToneHandler mDualToneHandler;
private int mUser;
- /**
- * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
- */
- private boolean mUseWallpaperTextColors;
-
private int mNonAdaptedSingleToneColor;
private int mNonAdaptedForegroundColor;
private int mNonAdaptedBackgroundColor;
@@ -242,31 +236,6 @@
mIsSubscribedForTunerUpdates = false;
}
- /**
- * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
- * revert back to dark-mode-based/tinted colors.
- *
- * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
- * components
- */
- public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
- if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
- return;
- }
-
- mUseWallpaperTextColors = shouldUseWallpaperTextColor;
-
- if (mUseWallpaperTextColors) {
- updateColors(
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
- } else {
- updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
- mNonAdaptedSingleToneColor);
- }
- }
-
public void setColorsFromContext(Context context) {
if (context == null) {
return;
@@ -476,13 +445,19 @@
mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
- if (!mUseWallpaperTextColors) {
- updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
- mNonAdaptedSingleToneColor);
- }
+ updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
+ mNonAdaptedSingleToneColor);
}
- private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
+ /**
+ * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
+ * if registered.
+ *
+ * @param foregroundColor
+ * @param backgroundColor
+ * @param singleToneColor
+ */
+ public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
mTextColor = singleToneColor;
if (mBatteryPercentView != null) {
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/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 247f25e..6b300f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@
}
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
cvh.action(BooleanAction(templateId, !isChecked))
}, true /* blockable */))
@@ -79,7 +80,7 @@
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
val blockable = cvh.usePanel()
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
if (cvh.usePanel()) {
showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@
}
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.action(FloatAction(templateId, newValue))
}, false /* blockable */))
}
override fun longPress(cvh: ControlViewHolder) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
// Long press snould only be called when there is valid control state, otherwise ignore
cvh.cws.control?.let {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@
}
override fun runPendingAction(controlId: String) {
+ if (!keyguardStateController.isUnlocked()) return
if (pendingAction?.controlId == controlId) {
pendingAction?.invoke()
pendingAction = null
@@ -135,7 +137,8 @@
false
}
- private fun bouncerOrRun(action: Action) {
+ @VisibleForTesting
+ fun bouncerOrRun(action: Action) {
if (keyguardStateController.isShowing()) {
var closeDialog = !keyguardStateController.isUnlocked()
if (closeDialog) {
@@ -190,6 +193,10 @@
}
}
+ @VisibleForTesting
+ fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
+ Action(controlId, f, blockable)
+
inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
fun invoke() {
if (!blockable || shouldRunAction(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index f533cfb..db68d17 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -28,11 +28,12 @@
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
+import javax.inject.Inject
/**
* Show the controls space inside a dialog, as from the lock screen.
*/
-class ControlsDialog(
+class ControlsDialog @Inject constructor(
thisContext: Context,
val broadcastDispatcher: BroadcastDispatcher
) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
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/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
package com.android.systemui.dagger;
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
import com.android.systemui.wmshell.WMShellModule;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
import dagger.Subcomponent;
/**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ * and {@link TvWMComponent} includes {@link TvWMShellModule}
*/
@WMSingleton
@Subcomponent(modules = {WMShellModule.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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c55fdf4..91cf710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -415,6 +415,7 @@
@Override
public void onUserSwitching(int userId) {
+ if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
// Note that the mLockPatternUtils user has already been updated from setCurrentUser.
// We need to force a reset of the views, since lockNow (called by
// ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -432,6 +433,7 @@
@Override
public void onUserSwitchComplete(int userId) {
+ if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
if (userId != UserHandle.USER_SYSTEM) {
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
// Don't try to dismiss if the user has Pin/Patter/Password set
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 76281d8..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,7 +29,10 @@
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;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
@@ -61,7 +64,11 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(subcomponents = {KeyguardStatusViewComponent.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/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index e8dba8f..c1db8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.log
import android.util.Log
-import com.android.systemui.dump.DumpManager
import com.android.systemui.log.dagger.LogModule
import java.io.PrintWriter
import java.text.SimpleDateFormat
@@ -58,7 +57,7 @@
* In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
* the first letter of any of the previous.
*
- * Buffers are provided by [LogModule].
+ * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
*
* @param name The name of this buffer
* @param maxLogs The maximum number of messages to keep in memory at any one time, including the
@@ -77,10 +76,6 @@
var frozen = false
private set
- fun attach(dumpManager: DumpManager) {
- dumpManager.registerBuffer(name, this)
- }
-
/**
* Logs a message to the log buffer
*
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
new file mode 100644
index 0000000..0622df3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import javax.inject.Inject
+
+@SysUISingleton
+class LogBufferFactory @Inject constructor(
+ private val dumpManager: DumpManager,
+ private val logcatEchoTracker: LogcatEchoTracker
+) {
+ @JvmOverloads
+ fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer {
+ val buffer = LogBuffer(name, maxPoolSize, flexSize, logcatEchoTracker)
+ dumpManager.registerBuffer(name, buffer)
+ return buffer
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index fff185b..19193f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,8 +22,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
import com.android.systemui.log.LogcatEchoTrackerProd;
@@ -40,96 +40,64 @@
@Provides
@SysUISingleton
@DozeLog
- public static LogBuffer provideDozeLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
+ return factory.create("DozeLog", 100);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@SysUISingleton
@NotificationLog
- public static LogBuffer provideNotificationsLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifLog", 1000, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifLog", 1000);
}
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
@NotificationSectionLog
- public static LogBuffer provideNotificationSectionLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifSectionLog", 1000);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@SysUISingleton
@NotifInteractionLog
- public static LogBuffer provideNotifInteractionLogBuffer(
- LogcatEchoTracker echoTracker,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotifInteractionLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifInteractionLog", 50);
}
/** Provides a logging buffer for all logs related to Quick Settings. */
@Provides
@SysUISingleton
@QSLog
- public static LogBuffer provideQuickSettingsLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
+ return factory.create("QSLog", 500);
}
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
@Provides
@SysUISingleton
@BroadcastDispatcherLog
- public static LogBuffer provideBroadcastDispatcherLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
+ return factory.create("BroadcastDispatcherLog", 500);
}
/** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
@Provides
@SysUISingleton
@ToastLog
- public static LogBuffer provideToastLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideToastLogBuffer(LogBufferFactory factory) {
+ return factory.create("ToastLog", 50);
}
/** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
@Provides
@SysUISingleton
@PrivacyLog
- public static LogBuffer providePrivacyLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer providePrivacyLogBuffer(LogBufferFactory factory) {
+ return factory.create("PrivacyLog", 100);
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 9353526..a3ff375 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,7 +1,9 @@
package com.android.systemui.media
+import android.animation.ArgbEvaluator
import android.content.Context
import android.content.Intent
+import android.content.res.ColorStateList
import android.content.res.Configuration
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.util.Log
@@ -112,6 +114,9 @@
private val visualStabilityCallback: VisualStabilityManager.Callback
private var needsReordering: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
+ private var bgColor = getBackgroundColor()
+ private var fgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
private var isRtl: Boolean = false
set(value) {
if (value != field) {
@@ -147,7 +152,7 @@
}
override fun onUiModeChanged() {
- // Only settings button needs to update for dark theme
+ recreatePlayers()
inflateSettingsButton()
}
}
@@ -249,6 +254,11 @@
}
private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
+ data.actions.forEach {
+ it.icon?.setTintList(ColorStateList.valueOf(fgColor))
+ }
+ data.appIcon?.setTintList(ColorStateList.valueOf(fgColor))
+ val dataCopy = data.copy(backgroundColor = bgColor)
val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
@@ -257,14 +267,14 @@
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.view?.player?.setLayoutParams(lp)
- newPlayer.bind(data, key)
+ newPlayer.bind(dataCopy, key)
newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(key, data, newPlayer)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers()
} else {
- existingPlayer.bind(data, key)
- MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+ existingPlayer.bind(dataCopy, key)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer)
if (visualStabilityManager.isReorderingAllowed) {
reorderAllPlayers()
} else {
@@ -298,12 +308,27 @@
}
private fun recreatePlayers() {
+ bgColor = getBackgroundColor()
+
+ fgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
+ pageIndicator.tintList = ColorStateList.valueOf(fgColor)
+
MediaPlayerData.mediaData().forEach { (key, data) ->
removePlayer(key, dismissMediaData = false)
addOrUpdatePlayer(key = key, oldKey = null, data = data)
}
}
+ private fun getBackgroundColor(): Int {
+ val themeAccent = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorAccent).defaultColor
+ val themeBackground = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorBackground).defaultColor
+ // Simulate transparency - cannot be actually transparent because of lockscreen
+ return ArgbEvaluator().evaluate(0.25f, themeBackground, themeAccent) as Int
+ }
+
private fun updatePageIndicator() {
val numPages = mediaContent.getChildCount()
pageIndicator.setNumPages(numPages)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3629d4d..55c55b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -25,7 +25,6 @@
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.Canvas
-import android.graphics.Color
import android.graphics.ImageDecoder
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
@@ -38,7 +37,6 @@
import android.service.notification.StatusBarNotification
import android.text.TextUtils
import android.util.Log
-import com.android.internal.graphics.ColorUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -48,7 +46,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
@@ -68,10 +65,6 @@
private const val TAG = "MediaDataManager"
private const val DEBUG = true
-private const val DEFAULT_LUMINOSITY = 0.25f
-private const val LUMINOSITY_THRESHOLD = 0.05f
-private const val SATURATION_MULTIPLIER = 0.8f
-const val DEFAULT_COLOR = Color.DKGRAY
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@@ -110,6 +103,11 @@
private val useQsMediaPlayer: Boolean
) : Dumpable {
+ private val themeText = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
+ private val bgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorBackground).defaultColor
+
// Internal listeners are part of the internal pipeline. External listeners (those registered
// with [MediaDeviceManager.addListener]) receive events after they have propagated through
// the internal pipeline.
@@ -395,7 +393,6 @@
} else {
null
}
- val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR
val mediaAction = getResumeMediaAction(resumeAction)
foregroundExecutor.execute {
@@ -449,7 +446,6 @@
}
}
}
- val bgColor = computeBackgroundColor(artworkBitmap)
// App name
val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -506,7 +502,7 @@
Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
} else {
action.getIcon()
- }
+ }.setTint(themeText)
val mediaAction = MediaAction(
mediaActionIcon,
runnable,
@@ -589,38 +585,9 @@
}
}
- private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int {
- var color = Color.WHITE
- if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) {
- // If we have valid art, get colors from that
- val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
- .generate()
- val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
- color = swatch.rgb
- } else {
- return DEFAULT_COLOR
- }
- // Adapt background color, so it's always subdued and text is legible
- val tmpHsl = floatArrayOf(0f, 0f, 0f)
- ColorUtils.colorToHSL(color, tmpHsl)
-
- val l = tmpHsl[2]
- // Colors with very low luminosity can have any saturation. This means that changing the
- // luminosity can make a black become red. Let's remove the saturation of very light or
- // very dark colors to avoid this issue.
- if (l < LUMINOSITY_THRESHOLD || l > 1f - LUMINOSITY_THRESHOLD) {
- tmpHsl[1] = 0f
- }
- tmpHsl[1] *= SATURATION_MULTIPLIER
- tmpHsl[2] = DEFAULT_LUMINOSITY
-
- color = ColorUtils.HSLToColor(tmpHsl)
- return color
- }
-
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- Icon.createWithResource(context, R.drawable.lb_ic_play),
+ Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
action,
context.getString(R.string.controls_media_resume)
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 4c96de2..553b6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -163,7 +163,8 @@
}
@Override
- public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+ public void setPlaybackProperties(IBinder token, float volume, boolean looping,
+ boolean hapticGeneratorEnabled) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -171,6 +172,7 @@
if (client != null) {
client.mRingtone.setVolume(volume);
client.mRingtone.setLooping(looping);
+ client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
}
// else no client for token when setting playback properties but will be set at play()
}
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/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index dd05484..7eb1fc1 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;
@@ -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,6 +614,22 @@
.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) {
@@ -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);
}
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 f56a890..fcb35e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
@@ -92,9 +93,10 @@
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSlider.Factory brightnessSliderFactory,
- @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+ @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+ FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
@@ -309,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/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index b02799f..9426e71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -34,6 +34,8 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.Utils;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -63,6 +65,7 @@
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
+ private final FeatureFlags mFeatureFlags;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
private int mLastOrientation;
@@ -93,11 +96,18 @@
private boolean mUsingHorizontalLayout;
- protected QSPanelControllerBase(T view, QSTileHost host,
+ protected QSPanelControllerBase(
+ T view,
+ QSTileHost host,
QSCustomizerController qsCustomizerController,
- @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager) {
+ @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+ MediaHost mediaHost,
+ MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger,
+ QSLogger qsLogger,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags
+ ) {
super(view);
mHost = host;
mQsCustomizerController = qsCustomizerController;
@@ -107,6 +117,7 @@
mUiEventLogger = uiEventLogger;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -334,9 +345,12 @@
}
boolean shouldUseHorizontalLayout() {
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources())) {
+ return false;
+ }
return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ && getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a0db200..383e932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -30,6 +30,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
import java.util.ArrayList;
import java.util.List;
@@ -57,9 +58,11 @@
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+ DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+ FeatureFlags featureFlags
+ ) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager);
+ uiEventLogger, qsLogger, dumpManager, featureFlags);
mUseSideLabels = qsLabelsFlag;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7776c12..87252ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -29,7 +29,6 @@
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.Pair;
-import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewGroup;
@@ -48,7 +47,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DualToneHandler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.privacy.OngoingPrivacyChip;
@@ -75,7 +73,6 @@
protected QuickQSPanel mHeaderQsPanel;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
- private DualToneHandler mDualToneHandler;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
@@ -110,8 +107,6 @@
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mDualToneHandler = new DualToneHandler(
- new ContextThemeWrapper(context, R.style.QSHeaderTheme));
}
@Override
@@ -149,10 +144,8 @@
}
void onAttach(TintedIconManager iconManager) {
- int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.colorForeground);
- float intensity = getColorIntensity(colorForeground);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
+ int fillColor = Utils.getColorAttrDefaultColor(getContext(),
+ android.R.attr.textColorPrimary);
// Set the correct tint for the status icons so they contrast
iconManager.setTint(fillColor);
@@ -271,14 +264,12 @@
int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
if (textColor != mTextColorPrimary) {
+ int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorSecondary);
mTextColorPrimary = textColor;
mClockView.setTextColor(textColor);
-
- float intensity = getColorIntensity(textColor);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
-
- Rect tintArea = new Rect(0, 0, 0, 0);
- mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
+ mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
+ mTextColorPrimary);
}
updateStatusIconAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 7d8d86f..ae0b5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -28,9 +28,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
-import com.android.systemui.qs.QuickStatusBarHeader;
import java.util.Objects;
@@ -40,10 +38,8 @@
private TextView mCarrierText;
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
- private DualToneHandler mDualToneHandler;
- private ColorStateList mColorForegroundStateList;
- private float mColorForegroundIntensity;
private CellSignalState mLastSignalState;
+ private boolean mProviderModel;
public QSCarrier(Context context) {
super(context);
@@ -64,21 +60,20 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mDualToneHandler = new DualToneHandler(getContext());
- mMobileGroup = findViewById(R.id.mobile_combo);
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+ mProviderModel = true;
} else {
- mMobileRoaming = findViewById(R.id.mobile_roaming);
+ mProviderModel = false;
}
+ mMobileGroup = findViewById(R.id.mobile_combo);
+ mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
- mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
-
- int colorForeground = Utils.getColorAttrDefaultColor(mContext,
- android.R.attr.colorForeground);
- mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
- mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
+ if (mProviderModel) {
+ mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
+ } else {
+ mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+ }
}
/**
@@ -92,26 +87,31 @@
mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
if (state.visible) {
mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
- ColorStateList colorStateList = ColorStateList.valueOf(
- mDualToneHandler.getSingleColor(mColorForegroundIntensity));
+ ColorStateList colorStateList = Utils.getColorAttr(mContext,
+ android.R.attr.textColorPrimary);
mMobileRoaming.setImageTintList(colorStateList);
mMobileSignal.setImageTintList(colorStateList);
- mMobileSignal.setImageLevel(state.mobileSignalIconId);
- StringBuilder contentDescription = new StringBuilder();
- if (state.contentDescription != null) {
- contentDescription.append(state.contentDescription).append(", ");
+ if (mProviderModel) {
+ mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
+ mMobileSignal.setContentDescription(state.contentDescription);
+ } else {
+ mMobileSignal.setImageLevel(state.mobileSignalIconId);
+ StringBuilder contentDescription = new StringBuilder();
+ if (state.contentDescription != null) {
+ contentDescription.append(state.contentDescription).append(", ");
+ }
+ if (state.roaming) {
+ contentDescription
+ .append(mContext.getString(R.string.data_connection_roaming))
+ .append(", ");
+ }
+ // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+ if (hasValidTypeContentDescription(state.typeContentDescription)) {
+ contentDescription.append(state.typeContentDescription);
+ }
+ mMobileSignal.setContentDescription(contentDescription);
}
- if (state.roaming) {
- contentDescription
- .append(mContext.getString(R.string.data_connection_roaming))
- .append(", ");
- }
- // TODO: show mobile data off/no internet text for 5 seconds before carrier text
- if (hasValidTypeContentDescription(state.typeContentDescription)) {
- contentDescription.append(state.typeContentDescription);
- }
- mMobileSignal.setContentDescription(contentDescription);
}
return true;
}
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 77200cc..aa6bbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -19,6 +19,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import android.annotation.MainThread;
+import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
@@ -26,13 +27,17 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
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;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -53,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;
@@ -62,6 +67,9 @@
new CellSignalState[SIM_SLOTS];
private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+ private int[] mLastSignalLevel = new int[SIM_SLOTS];
+ private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
+ private final boolean mProviderModel;
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@@ -72,6 +80,9 @@
CharSequence typeContentDescription,
CharSequence typeContentDescriptionHtml, CharSequence description,
boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+ if (mProviderModel) {
+ return;
+ }
int slotIndex = getSlotIndex(subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
@@ -92,6 +103,46 @@
}
@Override
+ public void setCallIndicator(NetworkController.IconState statusIcon, int subId) {
+ if (!mProviderModel) {
+ return;
+ }
+ int slotIndex = getSlotIndex(subId);
+ if (slotIndex >= SIM_SLOTS) {
+ Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+ return;
+ }
+ if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+ return;
+ }
+ if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+ if (statusIcon.visible) {
+ mInfos[slotIndex] = new CellSignalState(true,
+ statusIcon.icon, statusIcon.contentDescription, "", false);
+ } else {
+ // Whenever the no Calling & SMS state is cleared, switched to the last
+ // known call strength icon.
+ mInfos[slotIndex] = new CellSignalState(
+ true, mLastSignalLevel[slotIndex],
+ mLastSignalLevelDescription[slotIndex], "", false);
+ }
+ mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+ } else {
+ mLastSignalLevel[slotIndex] = statusIcon.icon;
+ mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
+ // Only Shows the call strength icon when the no Calling & SMS icon is not
+ // shown.
+ if (mInfos[slotIndex].mobileSignalIconId
+ != R.drawable.ic_qs_no_calling_sms) {
+ mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
+ statusIcon.contentDescription, "", false);
+ mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+ }
+ }
+ }
+
+ @Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
for (int i = 0; i < SIM_SLOTS; i++) {
@@ -102,7 +153,7 @@
}
};
- private static class Callback implements CarrierTextController.CarrierTextCallback {
+ private static class Callback implements CarrierTextManager.CarrierTextCallback {
private H mHandler;
Callback(H handler) {
@@ -110,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();
}
}
@@ -118,11 +169,16 @@
private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder) {
+ CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
+ if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ mProviderModel = true;
+ } else {
+ mProviderModel = false;
+ }
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
- mCarrierTextController = carrierTextControllerBuilder
+ mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(false)
.setShowMissingSim(false)
.build();
@@ -140,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();
@@ -149,7 +204,13 @@
mCarrierDividers[1] = view.getCarrierDivider2();
for (int i = 0; i < SIM_SLOTS; i++) {
- mInfos[i] = new CellSignalState();
+ mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms,
+ context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
+ "", false);
+ mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
+ mLastSignalLevelDescription[i] =
+ context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
+ .toString();
mCarrierGroups[i].setOnClickListener(onClickListener);
}
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -185,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);
}
}
@@ -215,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;
@@ -269,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;
@@ -287,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();
@@ -304,17 +365,19 @@
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) {
+ CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
mNetworkController = networkController;
mCarrierTextControllerBuilder = carrierTextControllerBuilder;
+ mContext = context;
}
public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -324,7 +387,7 @@
public QSCarrierGroupController build() {
return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
- mNetworkController, mCarrierTextControllerBuilder);
+ mNetworkController, mCarrierTextControllerBuilder, mContext);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index abf230e..d4bab21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -102,6 +102,12 @@
public void onConfigChanged(Configuration newConfig) {
mView.updateNavBackDrop(newConfig, mLightBarController);
mView.updateResources();
+ if (mTileAdapter.updateNumColumns()) {
+ RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager();
+ if (lm instanceof GridLayoutManager) {
+ ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns());
+ }
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 21464fd..048fdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -98,7 +98,7 @@
private final UiEventLogger mUiEventLogger;
private final AccessibilityDelegateCompat mAccessibilityDelegate;
private RecyclerView mRecyclerView;
- private final int mNumColumns;
+ private int mNumColumns;
@Inject
public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
@@ -123,6 +123,21 @@
mRecyclerView = null;
}
+ /**
+ * Update the number of columns to show, from resources.
+ *
+ * @return {@code true} if the number of columns changed, {@code false} otherwise
+ */
+ public boolean updateNumColumns() {
+ int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns);
+ if (numColumns != mNumColumns) {
+ mNumColumns = numColumns;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public int getNumColumns() {
return mNumColumns;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 11e6330..6983b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -37,6 +37,7 @@
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.DataSaverTile;
+import com.android.systemui.qs.tiles.DeviceControlsTile;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
@@ -89,6 +90,7 @@
private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
+ private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -123,7 +125,8 @@
Provider<ScreenRecordTile> screenRecordTileProvider,
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
Provider<CameraToggleTile> cameraToggleTileProvider,
- Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
+ Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
+ Provider<DeviceControlsTile> deviceControlsTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -153,6 +156,7 @@
mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
mCameraToggleTileProvider = cameraToggleTileProvider;
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
+ mDeviceControlsTileProvider = deviceControlsTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -212,6 +216,8 @@
return mCameraToggleTileProvider.get();
case "mictoggle":
return mMicrophoneToggleTileProvider.get();
+ case "controls":
+ return mDeviceControlsTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
new file mode 100644
index 0000000..4144591
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+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
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DeviceControlsTile @Inject constructor(
+ host: QSHost,
+ @Background backgroundLooper: Looper,
+ @Main mainHandler: Handler,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ private val controlsComponent: ControlsComponent,
+ private val featureFlags: FeatureFlags,
+ private val dialogProvider: Provider<ControlsDialog>,
+ globalSettings: GlobalSettings
+) : QSTileImpl<QSTile.State>(
+ host,
+ backgroundLooper,
+ mainHandler,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+) {
+
+ companion object {
+ const val SETTINGS_FLAG = "controls_lockscreen"
+ }
+
+ private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
+ private var hasControlsApps = AtomicBoolean(false)
+ private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+
+ private var controlsDialog: ControlsDialog? = null
+ private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+
+ private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
+ refreshState()
+ }
+ }
+ }
+
+ init {
+ controlsComponent.getControlsListingController().ifPresent {
+ it.observe(this, listingCallback)
+ }
+ }
+
+ override fun isAvailable(): Boolean {
+ return featureFlags.isKeyguardLayoutEnabled &&
+ controlsLockscreen &&
+ controlsComponent.getVisibility() != UNAVAILABLE
+ }
+
+ override fun newTileState(): QSTile.State {
+ return QSTile.State().also {
+ it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps`
+ }
+ }
+
+ override fun handleDestroy() {
+ dismissDialog()
+ super.handleDestroy()
+ }
+
+ private fun createDialog() {
+ if (controlsDialog?.isShowing != true) {
+ controlsDialog = dialogProvider.get()
+ }
+ }
+
+ private fun dismissDialog() {
+ controlsDialog?.dismiss()?.also {
+ controlsDialog = null
+ }
+ }
+
+ override fun handleClick() {
+ if (state.state != Tile.STATE_UNAVAILABLE) {
+ mUiHandler.post {
+ createDialog()
+ controlsDialog?.show(controlsComponent.getControlsUiController().get())
+ }
+ }
+ }
+
+ override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+ state.label = tileLabel
+ state.secondaryLabel = ""
+ state.stateDescription = ""
+ state.contentDescription = state.label
+ state.icon = icon
+ if (hasControlsApps.get()) {
+ state.state = Tile.STATE_ACTIVE
+ if (controlsDialog == null) {
+ mUiHandler.post(this::createDialog)
+ }
+ } else {
+ state.state = Tile.STATE_UNAVAILABLE
+ dismissDialog()
+ }
+ }
+
+ override fun getMetricsCategory(): Int {
+ return 0
+ }
+
+ override fun getLongClickIntent(): Intent {
+ return intent
+ }
+
+ override fun getTileLabel(): CharSequence {
+ return mContext.getText(R.string.quick_controls_title)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 191b85b..0abff77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -469,9 +469,8 @@
if (wifiConnected) {
minimalStateDescription.append(cb.mWifiSignalContentDescription);
minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
- if (!TextUtils.isEmpty(state.secondaryLabel)) {
- minimalContentDescription.append(",").append(state.secondaryLabel);
- }
+ } else if (!TextUtils.isEmpty(state.secondaryLabel)) {
+ minimalContentDescription.append(",").append(state.secondaryLabel);
}
}
state.stateDescription = minimalStateDescription.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6a8c6149..6ca550c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -41,7 +41,7 @@
protected static int layoutResId = R.layout.qs_user_detail_item;
private UserAvatarView mAvatar;
- private TextView mName;
+ protected TextView mName;
private int mActivatedStyle;
private int mRegularStyle;
private View mRestrictedPadlock;
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/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5438743..26781f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -277,12 +277,12 @@
*/
void end() {
mMediaRecorder.stop();
- mMediaProjection.stop();
mMediaRecorder.release();
- mMediaRecorder = null;
- mMediaProjection = null;
mInputSurface.release();
mVirtualDisplay.release();
+ mMediaProjection.stop();
+ mMediaRecorder = null;
+ mMediaProjection = null;
stopInternalAudioRecording();
Log.d(TAG, "end recording");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index c8afd0b..9383aef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,17 +16,22 @@
package com.android.systemui.screenshot;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.android.systemui.R;
@@ -35,6 +40,7 @@
* cropped out.
*/
public class CropView extends View {
+ private static final String TAG = "CropView";
public enum CropBoundary {
NONE, TOP, BOTTOM
}
@@ -118,10 +124,7 @@
case MotionEvent.ACTION_UP:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
// Commit the delta to the stored crop values.
- mTopCrop += mTopDelta;
- mBottomCrop += mBottomDelta;
- mTopDelta = 0;
- mBottomDelta = 0;
+ commitDeltas();
updateListener(event);
}
}
@@ -129,6 +132,42 @@
}
/**
+ * Animate the given boundary to the given value.
+ */
+ public void animateBoundaryTo(CropBoundary boundary, float value) {
+ if (boundary == CropBoundary.NONE) {
+ Log.w(TAG, "No boundary selected for animation");
+ return;
+ }
+ float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
+ : (value - mBottomCrop);
+ ValueAnimator animator = new ValueAnimator();
+ animator.addUpdateListener(animation -> {
+ if (boundary == CropBoundary.TOP) {
+ mTopDelta = animation.getAnimatedFraction() * totalDelta;
+ } else {
+ mBottomDelta = animation.getAnimatedFraction() * totalDelta;
+ }
+ invalidate();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ commitDeltas();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ commitDeltas();
+ }
+ });
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(750);
+ animator.setInterpolator(new FastOutSlowInInterpolator());
+ animator.start();
+ }
+
+ /**
* @return value [0,1] representing the position of the top crop boundary. Does not reflect
* changes from any in-progress touch input.
*/
@@ -148,6 +187,13 @@
mCropInteractionListener = listener;
}
+ private void commitDeltas() {
+ mTopCrop += mTopDelta;
+ mBottomCrop += mBottomDelta;
+ mTopDelta = 0;
+ mBottomDelta = 0;
+ }
+
private void updateListener(MotionEvent event) {
if (mCropInteractionListener != null) {
float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
index 212e6c8..a95c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -70,7 +70,7 @@
RecordingCanvas canvas = mNode.beginRecording(w, h);
canvas.save();
- canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
+ canvas.clipRect(0, 0, mLocation.width(), mLocation.height());
canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
0, 0, null);
canvas.restore();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 20f8451..ae3cd99 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -21,6 +21,8 @@
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.Log;
import androidx.annotation.UiThread;
@@ -32,11 +34,14 @@
* <p>
* To display on-screen, use {@link #getDrawable()}.
*/
-@UiThread
class ImageTileSet {
private static final String TAG = "ImageTileSet";
+ ImageTileSet(@UiThread Handler handler) {
+ mHandler = handler;
+ }
+
interface OnBoundsChangedListener {
/**
* Reports an update to the bounding box that contains all active tiles. These are virtual
@@ -54,6 +59,7 @@
private final List<ImageTile> mTiles = new ArrayList<>();
private final Rect mBounds = new Rect();
+ private final Handler mHandler;
private OnContentChangedListener mOnContentChangedListener;
private OnBoundsChangedListener mOnBoundsChangedListener;
@@ -73,13 +79,32 @@
newBounds.union(newRect);
if (!newBounds.equals(mBounds)) {
mBounds.set(newBounds);
- if (mOnBoundsChangedListener != null) {
- mOnBoundsChangedListener.onBoundsChanged(
- newBounds.left, newBounds.top, newBounds.right, newBounds.bottom);
- }
+ notifyBoundsChanged(mBounds);
}
- if (mOnContentChangedListener != null) {
+ notifyContentChanged();
+ }
+
+ void notifyContentChanged() {
+ if (mOnContentChangedListener == null) {
+ return;
+ }
+ if (mHandler.getLooper().isCurrentThread()) {
mOnContentChangedListener.onContentChanged();
+ } else {
+ mHandler.post(() -> mOnContentChangedListener.onContentChanged());
+ }
+ }
+
+ void notifyBoundsChanged(Rect bounds) {
+ if (mOnBoundsChangedListener == null) {
+ return;
+ }
+ if (mHandler.getLooper().isCurrentThread()) {
+ mOnBoundsChangedListener.onBoundsChanged(
+ bounds.left, bounds.top, bounds.right, bounds.bottom);
+ } else {
+ mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged(
+ bounds.left, bounds.top, bounds.right, bounds.bottom));
}
}
@@ -117,22 +142,16 @@
* getHeight()).
*/
Bitmap toBitmap(Rect bounds) {
+ Log.d(TAG, "exporting with bounds: " + bounds);
if (mTiles.isEmpty()) {
return null;
}
final RenderNode output = new RenderNode("Bitmap Export");
- output.setPosition(0, 0, getWidth(), getHeight());
+ output.setPosition(0, 0, bounds.width(), bounds.height());
RecordingCanvas canvas = output.beginRecording();
- canvas.translate(-getLeft(), -getTop());
- // Additional translation to account for the requested bounds
- canvas.translate(-bounds.left, -bounds.top);
- canvas.clipRect(bounds);
- for (ImageTile tile : mTiles) {
- canvas.save();
- canvas.translate(tile.getLeft(), tile.getTop());
- canvas.drawRenderNode(tile.getDisplayList());
- canvas.restore();
- }
+ Drawable drawable = getDrawable();
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
output.endRecording();
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
@@ -162,14 +181,13 @@
}
void clear() {
- mBounds.set(0, 0, 0, 0);
+ if (mBounds.isEmpty()) {
+ return;
+ }
+ mBounds.setEmpty();
mTiles.forEach(ImageTile::close);
mTiles.clear();
- if (mOnBoundsChangedListener != null) {
- mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0);
- }
- if (mOnContentChangedListener != null) {
- mOnContentChangedListener.onContentChanged();
- }
+ notifyBoundsChanged(mBounds);
+ notifyContentChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index f887151..f8f1d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -144,7 +144,9 @@
setAlpha(0f);
setTranslationX((getParentWidth() - getWidth()) / 2);
setVisibility(View.VISIBLE);
- animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start();
+ boolean touchOnRight = event.getX() > getParentWidth() / 2;
+ float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
+ animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start();
break;
case MotionEvent.ACTION_MOVE:
mLastCropPosition = cropPosition;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index bb07012..dc639dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -50,8 +50,6 @@
public class ScrollCaptureClient {
private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024);
private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed
- private static final int MAX_PAGES = 5;
- private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE;
@VisibleForTesting
static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
@@ -66,10 +64,11 @@
/**
* Session start should be deferred until UI is active because of resource allocation and
* potential visible side effects in the target window.
-
+ *
* @param sessionConsumer listener to receive the session once active
+ * @param maxPages the capture buffer size expressed as a multiple of the content height
*/
- void start(Consumer<Session> sessionConsumer);
+ void start(Consumer<Session> sessionConsumer, float maxPages);
/**
* Close the connection.
@@ -96,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
+ + '}';
+ }
}
/**
@@ -196,6 +206,7 @@
private int mTileWidth;
private Rect mRequestRect;
private boolean mStarted;
+ private int mMaxTiles;
private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
mConnectionConsumer = connectionConsumer;
@@ -285,12 +296,15 @@
// ScrollCaptureController.Connection
@Override
- public void start(Consumer<Session> sessionConsumer) {
+ public void start(Consumer<Session> sessionConsumer, float maxPages) {
if (DEBUG_SCROLL) {
- Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")");
+ Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
+ + " maxPages=" + maxPages + ")"
+ + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]");
}
+ mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
- MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
mSessionConsumer = sessionConsumer;
try {
mConnection.startCapture(mReader.getSurface());
@@ -345,7 +359,7 @@
@Override
public int getMaxTiles() {
- return MAX_IMAGE_COUNT;
+ return mMaxTiles;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 25438a6..ad5e637 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -34,6 +35,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -44,13 +46,23 @@
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Interaction controller between the UI and ScrollCaptureClient.
*/
public class ScrollCaptureController implements OnComputeInternalInsetsListener {
private static final String TAG = "ScrollCaptureController";
+ private static final float MAX_PAGES_DEFAULT = 3f;
+
+ private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
+
+ private static final int UP = -1;
+ private static final int DOWN = 1;
+
+ private int mDirection = DOWN;
+ private boolean mAtBottomEdge;
+ private boolean mAtTopEdge;
+ private Session mSession;
// TODO: Support saving without additional action.
private enum PendingAction {
@@ -59,7 +71,6 @@
SAVE
}
- public static final int MAX_PAGES = 5;
public static final int MAX_HEIGHT = 12000;
private final Connection mConnection;
@@ -91,7 +102,7 @@
mBgExecutor = bgExecutor;
mImageExporter = exporter;
mUiEventLogger = uiEventLogger;
- mImageTileSet = new ImageTileSet();
+ mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
}
/**
@@ -129,7 +140,9 @@
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
- mConnection.start(this::startCapture);
+ float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
+ SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
+ mConnection.start(this::startCapture, maxPages);
}
@@ -232,41 +245,82 @@
return mWindow.findViewById(res);
}
+
+ private void onCaptureResult(CaptureResult result) {
+ Log.d(TAG, "onCaptureResult: " + result);
+ boolean emptyResult = result.captured.height() == 0;
+ boolean partialResult = !emptyResult
+ && result.captured.height() < result.requested.height();
+ boolean finish = false;
+
+ if (partialResult || emptyResult) {
+ // Potentially reached a vertical boundary. Extend in the other direction.
+ switch (mDirection) {
+ case DOWN:
+ Log.d(TAG, "Reached bottom edge.");
+ mAtBottomEdge = true;
+ mDirection = UP;
+ break;
+ case UP:
+ Log.d(TAG, "Reached top edge.");
+ mAtTopEdge = true;
+ mDirection = DOWN;
+ break;
+ }
+
+ if (mAtTopEdge && mAtBottomEdge) {
+ Log.d(TAG, "Reached both top and bottom edge, ending.");
+ finish = true;
+ } else {
+ // only reverse if the edge was relatively close to the starting point
+ if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) {
+ Log.d(TAG, "Restarting in reverse direction.");
+
+ // Because of temporary limitations, we cannot just jump to the opposite edge
+ // and continue there. Instead, clear the results and start over capturing from
+ // here in the other direction.
+ mImageTileSet.clear();
+ } else {
+ Log.d(TAG, "Capture is tall enough, stopping here.");
+ finish = true;
+ }
+ }
+ }
+
+ if (!emptyResult) {
+ mImageTileSet.addTile(new ImageTile(result.image, result.captured));
+ }
+
+ Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
+ + " - " + mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
+ + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+
+
+ // Stop when "too tall"
+ if (mImageTileSet.size() >= mSession.getMaxTiles()
+ || mImageTileSet.getHeight() > MAX_HEIGHT) {
+ Log.d(TAG, "Max height and/or tile count reached.");
+ finish = true;
+ }
+
+ if (finish) {
+ Session session = mSession;
+ mSession = null;
+ Log.d(TAG, "Stop.");
+ mUiExecutor.execute(() -> afterCaptureComplete(session));
+ return;
+ }
+
+ int nextTop = (mDirection == DOWN) ? result.captured.bottom
+ : result.captured.top - mSession.getTileHeight();
+ Log.d(TAG, "requestTile: " + nextTop);
+ mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
+ }
+
private void startCapture(Session session) {
- Log.d(TAG, "startCapture");
- Consumer<ScrollCaptureClient.CaptureResult> consumer =
- new Consumer<ScrollCaptureClient.CaptureResult>() {
-
- int mFrameCount = 0;
- int mTop = 0;
-
- @Override
- public void accept(ScrollCaptureClient.CaptureResult result) {
- mFrameCount++;
-
- boolean emptyFrame = result.captured.height() == 0;
- if (!emptyFrame) {
- ImageTile tile = new ImageTile(result.image, result.captured);
- Log.d(TAG, "Adding tile: " + tile);
- mImageTileSet.addTile(tile);
- Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", "
- + "h=" + mImageTileSet.getHeight());
- }
-
- if (emptyFrame || mFrameCount >= MAX_PAGES
- || mTop + session.getTileHeight() > MAX_HEIGHT) {
-
- mUiExecutor.execute(() -> afterCaptureComplete(session));
- return;
- }
- mTop += result.captured.height();
- session.requestTile(mTop, /* consumer */ this);
- }
- };
-
- // fire it up!
- session.requestTile(0, consumer);
- };
+ mSession = session;
+ session.requestTile(0, this::onCaptureResult);
+ }
@UiThread
void afterCaptureComplete(Session session) {
@@ -277,6 +331,7 @@
} else {
mPreview.setImageDrawable(mImageTileSet.getDrawable());
mMagnifierView.setImageTileset(mImageTileSet);
+ mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
index 72f489b..4ec8eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
@@ -56,7 +56,7 @@
mNode = new RenderNode("TiledImageDrawable");
}
mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight());
- Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight());
+ Canvas canvas = mNode.beginRecording();
// Align content (virtual) top/left with 0,0, within the render node
canvas.translate(-mTiles.getLeft(), -mTiles.getTop());
for (int i = 0; i < mTiles.size(); i++) {
@@ -79,8 +79,8 @@
if (canvas.isHardwareAccelerated()) {
Rect bounds = getBounds();
canvas.save();
- canvas.clipRect(bounds);
- canvas.translate(bounds.left, bounds.top);
+ canvas.clipRect(0, 0, bounds.width(), bounds.height());
+ canvas.translate(-bounds.left, -bounds.top);
canvas.drawRenderNode(mNode);
canvas.restore();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
import android.util.Log;
import android.util.MathUtils;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2dd85e9..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -74,4 +74,12 @@
public boolean isPeopleTileEnabled() {
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
+
+ public boolean isToastStyleEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_toast_style);
+ }
+
+ public boolean isMonetEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_monet);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 0465ebf..edcf6d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -18,23 +18,19 @@
import static android.service.notification.NotificationListenerService.Ranking;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK;
+
import android.app.NotificationManager;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.util.Pair;
-import androidx.annotation.Nullable;
-
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
@@ -45,10 +41,10 @@
* should show an indicator.
*/
@SysUISingleton
-public class AssistantFeedbackController extends ContentObserver {
- private final Uri FEEDBACK_URI
- = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
- private ContentResolver mResolver;
+public class AssistantFeedbackController {
+ private final Context mContext;
+ private final Handler mHandler;
+ private final DeviceConfigProxy mDeviceConfigProxy;
public static final int STATUS_UNCHANGED = 0;
public static final int STATUS_ALERTED = 1;
@@ -56,34 +52,39 @@
public static final int STATUS_PROMOTED = 3;
public static final int STATUS_DEMOTED = 4;
- private boolean mFeedbackEnabled;
+ private volatile boolean mFeedbackEnabled;
+
+ private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) {
+ mFeedbackEnabled = properties.getBoolean(
+ ENABLE_NAS_FEEDBACK, false);
+ }
+ }
+ };
/** Injected constructor */
@Inject
- public AssistantFeedbackController(Context context) {
- super(new Handler(Looper.getMainLooper()));
- mResolver = context.getContentResolver();
- mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL);
- update(null);
+ public AssistantFeedbackController(@Main Handler handler,
+ Context context, DeviceConfigProxy proxy) {
+ mHandler = handler;
+ mContext = context;
+ mDeviceConfigProxy = proxy;
+ mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ENABLE_NAS_FEEDBACK, false);
+ mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ this::postToHandler, mPropertiesChangedListener);
}
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri, int flags) {
- update(uri);
- }
-
- @VisibleForTesting
- public void update(@Nullable Uri uri) {
- if (uri == null || FEEDBACK_URI.equals(uri)) {
- mFeedbackEnabled = Settings.Global.getInt(mResolver,
- Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0)
- != 0;
- }
+ private void postToHandler(Runnable r) {
+ this.mHandler.post(r);
}
/**
- * Determines whether to show any user controls related to the assistant. This is based on the
- * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED}
+ * Determines whether to show any user controls related to the assistant based on the
+ * DeviceConfig flag value
*/
public boolean isFeedbackEnabled() {
return mFeedbackEnabled;
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 57a64e4..a6daed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -55,11 +56,24 @@
private int mKeyguardStatusHeight;
/**
+ * 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 mUserSwitchHeight;
+
+ /**
* Preferred Y position of clock.
*/
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;
@@ -173,18 +187,22 @@
* Sets up algorithm values.
*/
public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
- float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
+ float panelExpansion, int parentHeight, int keyguardStatusHeight,
+ 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);
+ ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
+ + userSwitchHeight;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight;
+ mUserSwitchHeight = userSwitchHeight;
mClockPreferredY = clockPreferredY;
+ mUserSwitchPreferredY = userSwitchPreferredY;
mHasCustomClock = hasCustomClock;
mHasVisibleNotifs = hasVisibleNotifs;
mDarkAmount = dark;
@@ -198,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);
@@ -231,7 +250,7 @@
private int getExpandedPreferredClockY() {
if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
- return mMinTopMargin;
+ return mMinTopMargin + mUserSwitchHeight;
}
return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
: getExpandedClockPosition();
@@ -246,7 +265,8 @@
final int availableHeight = mMaxShadeBottom - mMinTopMargin;
final int containerCenter = mMinTopMargin + availableHeight / 2;
- float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
+ float y = containerCenter
+ - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT
- mClockNotificationsMargin - mNotificationStackHeight / 2;
if (y < mMinTopMargin) {
y = mMinTopMargin;
@@ -288,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
@@ -330,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/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5f547b5..33798d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import android.annotation.ColorInt;
@@ -25,6 +26,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -45,18 +47,15 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -75,18 +74,16 @@
private boolean mShowPercentAvailable;
private boolean mBatteryCharging;
- private boolean mKeyguardUserSwitcherShowing;
private boolean mBatteryListening;
private TextView mCarrierLabel;
- private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
private BatteryController mBatteryController;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private UserSwitcherController mUserSwitcherController;
+ private boolean mKeyguardUserSwitcherEnabled;
+ private final UserManager mUserManager;
private int mSystemIconsSwitcherHiddenExpandedMargin;
private int mSystemIconsBaseMargin;
@@ -109,13 +106,13 @@
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mUserManager = UserManager.get(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSystemIconsContainer = findViewById(R.id.system_icons_container);
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -124,7 +121,6 @@
mStatusIconContainer = findViewById(R.id.statusIcons);
loadDimens();
- updateUserSwitcher();
mBatteryController = Dependency.get(BatteryController.class);
}
@@ -137,14 +133,6 @@
R.dimen.multi_user_avatar_keyguard_size);
mMultiUserAvatar.setLayoutParams(lp);
- // Multi-user switch
- lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(
- R.dimen.multi_user_switch_width_keyguard);
- lp.setMarginEnd(getResources().getDimensionPixelSize(
- R.dimen.multi_user_switch_keyguard_margin));
- mMultiUserSwitch.setLayoutParams(lp);
-
// System icons
lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
lp.setMarginStart(getResources().getDimensionPixelSize(
@@ -194,22 +182,28 @@
}
private void updateVisibilities() {
- if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
- if (mMultiUserSwitch.getParent() != null) {
- getOverlay().remove(mMultiUserSwitch);
+ if (mMultiUserAvatar.getParent() != mStatusIconArea
+ && !mKeyguardUserSwitcherEnabled) {
+ if (mMultiUserAvatar.getParent() != null) {
+ getOverlay().remove(mMultiUserAvatar);
}
- mStatusIconArea.addView(mMultiUserSwitch, 0);
- } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
- mStatusIconArea.removeView(mMultiUserSwitch);
+ mStatusIconArea.addView(mMultiUserAvatar, 0);
+ } else if (mMultiUserAvatar.getParent() == mStatusIconArea
+ && mKeyguardUserSwitcherEnabled) {
+ mStatusIconArea.removeView(mMultiUserAvatar);
}
- if (mKeyguardUserSwitcher == null) {
+ if (!mKeyguardUserSwitcherEnabled) {
// If we have no keyguard switcher, the screen width is under 600dp. In this case,
// we only show the multi-user switch if it's enabled through UserManager as well as
// by the user.
- if (mMultiUserSwitch.isMultiUserEnabled()) {
- mMultiUserSwitch.setVisibility(View.VISIBLE);
+ // TODO(b/138661450) Move IPC calls to background
+ boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
+ mContext.getResources().getBoolean(
+ R.bool.qs_show_user_switcher_for_single_user)));
+ if (isMultiUserEnabled) {
+ mMultiUserAvatar.setVisibility(View.VISIBLE);
} else {
- mMultiUserSwitch.setVisibility(View.GONE);
+ mMultiUserAvatar.setVisibility(View.GONE);
}
}
mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
@@ -220,11 +214,12 @@
(LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
// If the avatar icon is gone, we need to have some end margin to display the system icons
// correctly.
- int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+ int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
? mSystemIconsBaseMargin
: 0;
- int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
- baseMarginEnd;
+ int marginEnd =
+ mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
+ : baseMarginEnd;
marginEnd = calculateMargin(marginEnd, mPadding.second);
if (marginEnd != lp.getMarginEnd()) {
lp.setMarginEnd(marginEnd);
@@ -334,20 +329,11 @@
}
}
- private void updateUserSwitcher() {
- boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
- mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
- mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
- mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
- }
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
userInfoController.addCallback(this);
- mUserSwitcherController = Dependency.get(UserSwitcherController.class);
- mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
@@ -369,11 +355,6 @@
mMultiUserAvatar.setImageDrawable(picture);
}
- /** */
- public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) {
- mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer);
- }
-
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
if (mBatteryCharging != charging) {
@@ -387,54 +368,42 @@
// could not care less
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
- updateUserSwitcher();
- }
-
- public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
- mKeyguardUserSwitcherShowing = showing;
- if (animate) {
- animateNextLayoutChange();
- }
- updateVisibilities();
- updateLayoutConsideringCutout();
- updateSystemIconsLayoutParams();
+ public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ mKeyguardUserSwitcherEnabled = enabled;
}
private void animateNextLayoutChange() {
final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
- final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
+ final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
- boolean userSwitcherHiding = userSwitcherVisible
- && mMultiUserSwitch.getParent() != mStatusIconArea;
+ boolean userAvatarHiding = userAvatarVisible
+ && mMultiUserAvatar.getParent() != mStatusIconArea;
mSystemIconsContainer.setX(systemIconsCurrentX);
mSystemIconsContainer.animate()
.translationX(0)
.setDuration(400)
- .setStartDelay(userSwitcherHiding ? 300 : 0)
+ .setStartDelay(userAvatarHiding ? 300 : 0)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.start();
- if (userSwitcherHiding) {
- getOverlay().add(mMultiUserSwitch);
- mMultiUserSwitch.animate()
+ if (userAvatarHiding) {
+ getOverlay().add(mMultiUserAvatar);
+ mMultiUserAvatar.animate()
.alpha(0f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
.withEndAction(() -> {
- mMultiUserSwitch.setAlpha(1f);
- getOverlay().remove(mMultiUserSwitch);
+ mMultiUserAvatar.setAlpha(1f);
+ getOverlay().remove(mMultiUserAvatar);
})
.start();
} else {
- mMultiUserSwitch.setAlpha(0f);
- mMultiUserSwitch.animate()
+ mMultiUserAvatar.setAlpha(0f);
+ mMultiUserAvatar.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(200)
@@ -452,8 +421,8 @@
if (visibility != View.VISIBLE) {
mSystemIconsContainer.animate().cancel();
mSystemIconsContainer.setTranslationX(0);
- mMultiUserSwitch.animate().cancel();
- mMultiUserSwitch.setAlpha(1f);
+ mMultiUserAvatar.animate().cancel();
+ mMultiUserAvatar.setAlpha(1f);
} else {
updateVisibilities();
updateSystemIconsLayoutParams();
@@ -523,9 +492,9 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryCharging: " + mBatteryCharging);
- pw.println(" mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing);
pw.println(" mBatteryListening: " + mBatteryListening);
pw.println(" mLayoutState: " + mLayoutState);
+ pw.println(" mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
if (mBatteryView != null) {
mBatteryView.dump(fd, pw, args);
}
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 480d3f4..16f36b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -35,7 +35,6 @@
import com.android.systemui.R;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSDetailDisplayer;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
@@ -44,8 +43,6 @@
public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
protected QSDetailDisplayer mQSDetailDisplayer;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private boolean mKeyguardMode;
private UserSwitcherController.BaseUserAdapter mUserListener;
final UserManager mUserManager;
@@ -85,15 +82,6 @@
refreshContentDescription();
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
- public void setKeyguardMode(boolean keyguardShowing) {
- mKeyguardMode = keyguardShowing;
- registerListener();
- }
-
public boolean isMultiUserEnabled() {
// TODO(b/138661450) Move IPC calls to background
return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
@@ -123,11 +111,7 @@
@Override
public void onClick(View v) {
- if (mKeyguardMode) {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.show(true /* animate */);
- }
- } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
+ if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
View center = getChildCount() > 0 ? getChildAt(0) : this;
int[] tmpInt = new int[2];
@@ -184,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 e0ef3b6..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;
@@ -48,6 +52,7 @@
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.UserManager;
import android.util.Log;
import android.util.MathUtils;
import android.view.DisplayCutout;
@@ -57,6 +62,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
@@ -64,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;
@@ -75,7 +83,10 @@
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;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -92,6 +103,7 @@
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;
@@ -131,9 +143,12 @@
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.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Utils;
import com.android.wm.shell.animation.FlingAnimationUtils;
import java.io.FileDescriptor;
@@ -283,6 +298,21 @@
}
};
+ final KeyguardUserSwitcherController.KeyguardUserSwitcherListener
+ mKeyguardUserSwitcherListener =
+ new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
+ @Override
+ public void onKeyguardUserSwitcherChanged(boolean open) {
+ if (mKeyguardUserSwitcherController == null) {
+ updateUserSwitcherVisibility(false);
+ } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+ updateUserSwitcherVisibility(open
+ && mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isKeyguardFadingAway());
+ }
+ }
+ };
+
private final LayoutInflater mLayoutInflater;
private final PowerManager mPowerManager;
private final AccessibilityManager mAccessibilityManager;
@@ -295,6 +325,9 @@
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;
@@ -307,8 +340,11 @@
private int mMaxAllowedKeyguardNotifications;
private KeyguardAffordanceHelper mAffordanceHelper;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+ private boolean mKeyguardUserSwitcherIsShowing;
+ private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
+ private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
@@ -332,6 +368,8 @@
private boolean mQsExpandedWhenExpandingStarted;
private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
+ private boolean mKeyguardQsUserSwitchEnabled;
+ private boolean mKeyguardUserSwitcherEnabled;
private boolean mDozing;
private boolean mDozingOnDown;
private int mBarState;
@@ -464,6 +502,7 @@
private final CommandQueue mCommandQueue;
private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final UserManager mUserManager;
private final ShadeController mShadeController;
private final MediaDataManager mMediaDataManager;
private int mDisplayId;
@@ -556,11 +595,15 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+ KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
+ KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+ KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
+ QSDetailDisplayer qsDetailDisplayer,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
AuthController authController,
- QSDetailDisplayer qsDetailDisplayer,
ScrimController scrimController,
+ UserManager userManager,
MediaDataManager mediaDataManager,
AmbientState ambientState,
FeatureFlags featureFlags,
@@ -581,8 +624,16 @@
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
- mQSDetailDisplayer = qsDetailDisplayer;
+ 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;
@@ -598,6 +649,7 @@
mDozeParameters = dozeParameters;
mBiometricUnlockController = biometricUnlockController;
mScrimController = scrimController;
+ mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mControlsComponent = controlsComponent;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -660,9 +712,25 @@
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
+
+ UserAvatarView userAvatarView = null;
+ KeyguardUserSwitcherView keyguardUserSwitcherView = null;
+
+ if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
+ 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(
R.id.notification_stack_scroller);
@@ -707,6 +775,10 @@
});
mView.setAccessibilityDelegate(mAccessibilityDelegate);
+ // dynamically apply the split shade value overrides.
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ updateResources();
+ }
}
@Override
@@ -735,17 +807,57 @@
R.dimen.heads_up_status_bar_padding);
}
- private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+ private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+ UserAvatarView userAvatarView,
+ KeyguardStatusBarView keyguardStatusBarView,
+ KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
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();
keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+
+ if (mKeyguardUserSwitcherController != null) {
+ // Try to close the switcher so that callbacks are triggered if necessary.
+ // Otherwise, NPV can get into a state where some of the views are still hidden
+ mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
+ mKeyguardUserSwitcherController.removeCallback();
+ }
+
+ mKeyguardQsUserSwitchController = null;
+ mKeyguardUserSwitcherController = null;
+
+ // Re-associate the KeyguardUserSwitcherController
+ 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 {
+ mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+ }
}
/**
@@ -783,16 +895,21 @@
mNotificationStackScrollLayoutController.setLayoutParams(lp);
}
- if (shouldUseSplitNotificationShade()) {
- // In order to change the constraints at runtime, all children of the Constraint Layout
- // must have ids.
- ensureAllViewsHaveIds(mNotificationContainerParent);
+ // 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)) {
+ 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);
}
- }
-
- private boolean shouldUseSplitNotificationShade() {
- return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
- && mResources.getBoolean(R.bool.config_use_split_notification_shade);
+ constraintSet.applyTo(mNotificationContainerParent);
}
private static void ensureAllViewsHaveIds(ViewGroup parentView) {
@@ -804,7 +921,27 @@
}
}
+ 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.
KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
int index = mView.indexOfChild(keyguardStatusView);
@@ -813,8 +950,28 @@
R.layout.keyguard_status_view, mView, false);
mView.addView(keyguardStatusView, index);
+ // Re-inflate the keyguard user switcher group.
+ 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);
+ updateViewControllers(
+ keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
@@ -838,6 +995,20 @@
false,
false,
mBarState);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
+ }
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
+ }
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -930,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(),
@@ -942,7 +1118,8 @@
? mKeyguardStatusViewController.getHeight()
: (int) (mKeyguardStatusViewController.getHeight()
- mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
- clockPreferredY, hasCustomClock(),
+ userIconHeight,
+ clockPreferredY, userSwitcherPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
mUpdateMonitor.canShowLockIcon(),
@@ -952,6 +1129,18 @@
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.userSwitchY,
+ animateClock);
+ }
updateNotificationTranslucency();
updateClock();
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1092,6 +1281,12 @@
private void updateClock() {
mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+ }
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+ }
}
public void animateToFullShade(long delay) {
@@ -1180,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);
@@ -1773,8 +1974,9 @@
mBarState != KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
- if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+ if (mKeyguardUserSwitcherController != null && mQsExpanded
+ && !mStackScrollerOverscrolling) {
+ mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
}
if (mQs == null) return;
mQs.setExpanded(mQsExpanded);
@@ -1841,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)) {
@@ -2480,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();
@@ -2608,10 +2816,6 @@
}
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
public void onScreenTurningOn() {
mKeyguardStatusViewController.dozeTimeTick();
}
@@ -2726,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;
@@ -3078,6 +3282,20 @@
true /* keyguardFadingAway */,
false /* goingToFullShade */,
mBarState);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ false /* goingToFullShade */,
+ mBarState);
+ }
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ false /* goingToFullShade */,
+ mBarState);
+ }
}
/**
@@ -3301,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);
@@ -3320,6 +3538,50 @@
return mNotificationStackScrollLayoutController;
}
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
+ * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ public boolean closeUserSwitcherIfOpen() {
+ if (mKeyguardUserSwitcherController != null) {
+ return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
+ true /* animate */);
+ }
+ return false;
+ }
+
+ 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(
+ mBarState,
+ true /* keyguardFadingAway */,
+ true /* goingToFullShade */,
+ mBarState);
+ setKeyguardBottomAreaVisibility(mBarState, true);
+ mNotificationContainerParent.setVisibility(View.GONE);
+ } else {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ StatusBarState.KEYGUARD,
+ false,
+ false,
+ StatusBarState.SHADE_LOCKED);
+ setKeyguardBottomAreaVisibility(mBarState, false);
+ mNotificationContainerParent.setVisibility(View.VISIBLE);
+ }
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3620,6 +3882,7 @@
private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
@Override
public void onThemeChanged() {
+ if (DEBUG) Log.d(TAG, "onThemeChanged");
final int themeResId = mView.getContext().getThemeResId();
if (mThemeResId == themeResId) {
return;
@@ -3631,11 +3894,15 @@
@Override
public void onOverlayChanged() {
+ if (DEBUG) Log.d(TAG, "onOverlayChanged");
reInflateViews();
}
@Override
- public void onUiModeChanged() {}
+ public void onDensityOrFontScaleChanged() {
+ if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
+ reInflateViews();
+ }
}
private class StatusBarStateListener implements StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index b367406..e394ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,8 +22,6 @@
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewStub;
-import android.view.ViewStub.OnInflateListener;
import android.view.WindowInsets;
import android.widget.FrameLayout;
@@ -44,14 +42,11 @@
* The container with notification stack scroller and quick settings inside.
*/
public class NotificationsQuickSettingsContainer extends ConstraintLayout
- implements OnInflateListener, FragmentListener,
- AboveShelfObserver.HasViewAboveShelfChangedListener {
+ implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
private FrameLayout mQsFrame;
- private View mUserSwitcher;
private NotificationStackScrollLayout mStackScroller;
private View mKeyguardStatusBar;
- private boolean mInflated;
private boolean mQsExpanded;
private boolean mCustomizerAnimating;
@@ -73,9 +68,6 @@
mStackScroller = findViewById(R.id.notification_stack_scroller);
mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
- ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
- userSwitcher.setOnInflateListener(this);
- mUserSwitcher = userSwitcher;
}
@Override
@@ -119,10 +111,6 @@
// touches first but the panel gets drawn above.
mDrawingOrderedChildren.clear();
mLayoutDrawingOrder.clear();
- if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) {
- mDrawingOrderedChildren.add(mUserSwitcher);
- mLayoutDrawingOrder.add(mUserSwitcher);
- }
if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
mDrawingOrderedChildren.add(mKeyguardStatusBar);
mLayoutDrawingOrder.add(mKeyguardStatusBar);
@@ -158,14 +146,6 @@
}
@Override
- public void onInflate(ViewStub stub, View inflated) {
- if (stub == mUserSwitcher) {
- mUserSwitcher = inflated;
- mInflated = true;
- }
- }
-
- @Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
QS container = (QS) fragment;
container.setContainer(this);
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 1e19bee..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -89,7 +89,6 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
@@ -226,7 +225,6 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -623,7 +621,6 @@
}
};
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
private final UserSwitcherController mUserSwitcherController;
private final NetworkController mNetworkController;
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -1212,9 +1209,6 @@
});
mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
- if (UserManager.get(mContext).isUserSwitcherEnabled()) {
- createUserSwitcher();
- }
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
@@ -1441,9 +1435,6 @@
// TODO: Bring these out of StatusBar.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
mUserSwitcherController.onDensityOrFontScaleChanged();
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
- }
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
mHeadsUpManager.onDensityOrFontScaleChanged();
}
@@ -1477,13 +1468,6 @@
}
}
- protected void createUserSwitcher() {
- mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
- mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
- mNotificationPanelViewController);
- }
-
private void inflateStatusBarWindow() {
mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
@@ -3266,7 +3250,7 @@
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
- } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){
+ } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
updatePanelExpansionForKeyguard();
@@ -3565,15 +3549,15 @@
}
return true;
}
+ if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
+ return true;
+ }
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
}
return true;
}
- if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) {
- return true;
- }
return false;
}
@@ -3622,20 +3606,8 @@
updateTheme();
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("StatusBar#updateKeyguardState");
- if (mState == StatusBarState.KEYGUARD) {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.setKeyguard(true,
- mStatusBarStateController.fromShadeLocked());
- }
- if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables();
- } else {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.setKeyguard(false,
- mStatusBarStateController.goingToFullShade() ||
- mState == StatusBarState.SHADE_LOCKED ||
- mStatusBarStateController.fromShadeLocked());
- }
-
+ if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+ mStatusBarView.removePendingHideExpandedRunnables();
}
updateDozingState();
checkBarModes();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 00acd7b..8620376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -42,8 +42,8 @@
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.List;
@@ -66,7 +66,7 @@
/**
* Display the no calling & SMS icons.
*/
- void setNoCallingIcons(String slot, List<NoCallingIconState> states);
+ void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states);
public void setIconVisibility(String slot, boolean b);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 5e8d590..f0c8527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -34,8 +34,8 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -206,6 +206,7 @@
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
+
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -218,23 +219,25 @@
}
/**
- * Accept a list of NoCallingIconStates, and show them in the same slot
+ * Accept a list of CallIndicatorIconStates, and show them in the same slot
* @param slot StatusBar slot
* @param states All of the no Calling & SMS icon states
*/
@Override
- public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+ public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
Slot noCallingSlot = getSlot(slot);
int slotIndex = getSlotIndex(slot);
-
- for (NoCallingIconState state : states) {
+ for (CallIndicatorIconState state : states) {
StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
if (holder == null) {
- holder = StatusBarIconHolder.fromNoCallingState(mContext, state);
- holder.setVisible(state.visible);
+ holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
setIcon(slotIndex, holder);
} else {
- holder.setVisible(state.visible);
+ int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+ String contentDescription = state.isNoCalling
+ ? state.noCallingDescription : state.callStrengthDescription;
+ holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
+ Icon.createWithResource(mContext, resId), 0, 0, contentDescription));
setIcon(slotIndex, holder);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 36a0e63..a1a2d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -22,8 +22,8 @@
import android.os.UserHandle;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
/**
@@ -72,14 +72,18 @@
}
/**
- * Creates a new StatusBarIconHolder from a NoCallingIconState.
+ * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
- public static StatusBarIconHolder fromNoCallingState(
- Context context, NoCallingIconState state) {
+ public static StatusBarIconHolder fromCallIndicatorState(
+ Context context, CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
+ int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+ String contentDescription = state.isNoCalling
+ ? state.noCallingDescription : state.callStrengthDescription;
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, state.resId), 0, 0, null);
+ Icon.createWithResource(context, resId), 0, 0, contentDescription);
holder.mTag = state.subId;
+ holder.setVisible(true);
return holder;
}
@@ -92,6 +96,10 @@
return mIcon;
}
+ public void setIcon(StatusBarIcon icon) {
+ mIcon = icon;
+ }
+
@Nullable
public WifiIconState getWifiState() {
return mWifiState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index f6165f6..7bc1bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -22,6 +22,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -39,7 +40,7 @@
public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
SecurityController.SecurityControllerCallback, Tunable {
private static final String TAG = "StatusBarSignalPolicy";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.INFO);
private final String mSlotAirplane;
private final String mSlotMobile;
@@ -67,7 +68,8 @@
private boolean mWifiVisible = false;
private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
- private ArrayList<NoCallingIconState> mNoCallingStates = new ArrayList<NoCallingIconState>();
+ private ArrayList<CallIndicatorIconState> mCallIndicatorStates =
+ new ArrayList<CallIndicatorIconState>();
private WifiIconState mWifiIconState = new WifiIconState();
public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
@@ -201,19 +203,25 @@
}
@Override
- public void setNoCallingStatus(boolean noCalling, int subId) {
+ public void setCallIndicator(IconState statusIcon, int subId) {
if (DEBUG) {
- Log.d(TAG, "setNoCallingStatus: "
- + "noCalling = " + noCalling + ","
+ Log.d(TAG, "setCallIndicator: "
+ + "statusIcon = " + statusIcon + ","
+ "subId = " + subId);
}
- NoCallingIconState state = getNoCallingState(subId);
+ CallIndicatorIconState state = getNoCallingState(subId);
if (state == null) {
return;
}
- state.visible = noCalling;
- mIconController.setNoCallingIcons(
- mSlotNoCalling, NoCallingIconState.copyStates(mNoCallingStates));
+ if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+ state.isNoCalling = statusIcon.visible;
+ state.noCallingDescription = statusIcon.contentDescription;
+ } else {
+ state.callStrengthResId = statusIcon.icon;
+ state.callStrengthDescription = statusIcon.contentDescription;
+ }
+ mIconController.setCallIndicatorIcons(
+ mSlotNoCalling, CallIndicatorIconState.copyStates(mCallIndicatorStates));
}
@Override
@@ -273,8 +281,8 @@
}
}
- private NoCallingIconState getNoCallingState(int subId) {
- for (NoCallingIconState state : mNoCallingStates) {
+ private CallIndicatorIconState getNoCallingState(int subId) {
+ for (CallIndicatorIconState state : mCallIndicatorStates) {
if (state.subId == subId) {
return state;
}
@@ -315,23 +323,25 @@
}
mIconController.removeAllIconsForSlot(mSlotMobile);
+ mIconController.removeAllIconsForSlot(mSlotNoCalling);
mMobileStates.clear();
- List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
- noCallingStates.addAll(mNoCallingStates);
- mNoCallingStates.clear();
+ List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
+ noCallingStates.addAll(mCallIndicatorStates);
+ mCallIndicatorStates.clear();
final int n = subs.size();
for (int i = 0; i < n; i++) {
mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
boolean isNewSub = true;
- for (NoCallingIconState state : noCallingStates) {
+ for (CallIndicatorIconState state : noCallingStates) {
if (state.subId == subs.get(i).getSubscriptionId()) {
- mNoCallingStates.add(state);
+ mCallIndicatorStates.add(state);
isNewSub = false;
break;
}
}
if (isNewSub) {
- mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ mCallIndicatorStates.add(
+ new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
}
}
}
@@ -425,14 +435,18 @@
/**
* Stores the StatusBar state for no Calling & SMS.
*/
- public static class NoCallingIconState {
- public boolean visible;
- public int resId;
+ public static class CallIndicatorIconState {
+ public boolean isNoCalling;
+ public int noCallingResId;
+ public int callStrengthResId;
public int subId;
+ public String noCallingDescription;
+ public String callStrengthDescription;
- private NoCallingIconState(int subId) {
+ private CallIndicatorIconState(int subId) {
this.subId = subId;
- this.resId = R.drawable.ic_qs_no_calling_sms;
+ this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+ this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
}
@Override
@@ -441,27 +455,36 @@
if (o == null || getClass() != o.getClass()) {
return false;
}
- NoCallingIconState that = (NoCallingIconState) o;
- return visible == that.visible
- && resId == that.resId
- && subId == that.subId;
+ CallIndicatorIconState that = (CallIndicatorIconState) o;
+ return isNoCalling == that.isNoCalling
+ && noCallingResId == that.noCallingResId
+ && callStrengthResId == that.callStrengthResId
+ && subId == that.subId
+ && noCallingDescription == that.noCallingDescription
+ && callStrengthDescription == that.callStrengthDescription;
+
}
@Override
public int hashCode() {
- return Objects.hash(visible, resId, subId);
+ return Objects.hash(isNoCalling, noCallingResId,
+ callStrengthResId, subId, noCallingDescription, callStrengthDescription);
}
- private void copyTo(NoCallingIconState other) {
- other.visible = visible;
- other.resId = resId;
+ private void copyTo(CallIndicatorIconState other) {
+ other.isNoCalling = isNoCalling;
+ other.noCallingResId = noCallingResId;
+ other.callStrengthResId = callStrengthResId;
other.subId = subId;
+ other.noCallingDescription = noCallingDescription;
+ other.callStrengthDescription = callStrengthDescription;
}
- private static List<NoCallingIconState> copyStates(List<NoCallingIconState> inStates) {
- ArrayList<NoCallingIconState> outStates = new ArrayList<>();
- for (NoCallingIconState state : inStates) {
- NoCallingIconState copy = new NoCallingIconState(state.subId);
+ private static List<CallIndicatorIconState> copyStates(
+ List<CallIndicatorIconState> inStates) {
+ ArrayList<CallIndicatorIconState> outStates = new ArrayList<>();
+ for (CallIndicatorIconState state : inStates) {
+ CallIndicatorIconState copy = new CallIndicatorIconState(state.subId);
state.copyTo(copy);
outStates.add(copy);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index ccaa1f4..5ff8970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -36,6 +36,7 @@
* the current or specified Looper.
*/
public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+ private static final String TAG = "CallbackHandler";
private static final int MSG_EMERGENCE_CHANGED = 0;
private static final int MSG_SUBS_CHANGED = 1;
private static final int MSG_NO_SIM_VISIBLE_CHANGED = 2;
@@ -198,17 +199,17 @@
}
@Override
- public void setNoCallingStatus(boolean noCalling, int subId) {
+ public void setCallIndicator(IconState statusIcon, int subId) {
String log = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setNoCallingStatus: ")
- .append("noCalling=").append(noCalling).append(",")
+ .append("setCallIndicator: ")
+ .append("statusIcon=").append(statusIcon).append(",")
.append("subId=").append(subId)
.toString();
recordLastCallback(log);
post(() -> {
for (SignalCallback signalCluster : mSignalCallbacks) {
- signalCluster.setNoCallingStatus(noCalling, subId);
+ signalCluster.setCallIndicator(statusIcon, subId);
}
});
}
@@ -226,24 +227,11 @@
@Override
public void setNoSims(boolean show, boolean simDetected) {
- String log = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setNoSims: ")
- .append("show=").append(show).append(",")
- .append("simDetected=").append(simDetected)
- .toString();
- recordLastCallback(log);
obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
}
@Override
public void setMobileDataEnabled(boolean enabled) {
- String log = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setMobileDataEnabled: ")
- .append("enabled=").append(enabled)
- .toString();
- recordLastCallback(log);
obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
}
@@ -283,7 +271,8 @@
}
protected void recordLastCallback(String callback) {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+ mHistory[mHistoryIndex] = callback;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
/**
@@ -293,7 +282,9 @@
pw.println(" - CallbackHandler -----");
int size = 0;
for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mHistory[i] != null) size++;
+ if (mHistory[i] != null) {
+ size++;
+ }
}
// Print out the previous states in ordered number.
for (int i = mHistoryIndex + HISTORY_SIZE - 1;
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/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 07433e1..0649478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -17,8 +17,15 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
+import android.graphics.Color;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import androidx.core.graphics.ColorUtils;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
@@ -27,6 +34,14 @@
*/
public class KeyguardUserDetailItemView extends UserDetailItemView {
+ private static final String TAG = "KeyguardUserDetailItemView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final int ANIMATION_DURATION_FADE_NAME = 240;
+
+ private float mDarkAmount;
+ private int mTextColor;
+
public KeyguardUserDetailItemView(Context context) {
this(context, null);
}
@@ -48,4 +63,89 @@
protected int getFontSizeDimen() {
return R.dimen.kg_user_switcher_text_size;
}
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTextColor = mName.getCurrentTextColor();
+ updateDark();
+ }
+
+ /**
+ * Update visibility of this view.
+ *
+ * @param showItem If true, this item is visible on the screen to the user. Generally this
+ * means that the item would be clickable. If false, item visibility will be
+ * set to GONE and hidden entirely.
+ * @param showTextName Whether or not the name should be shown next to the icon. If false,
+ * only the icon is shown.
+ * @param animate Whether the transition should be animated. Note, this only applies to
+ * animating the text name. The item itself will not animate (i.e. fade in/out).
+ * Instead, we delegate that to the parent view.
+ */
+ void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
+ showItem, showTextName, animate));
+ }
+
+ getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
+
+ if (showItem) {
+ if (showTextName) {
+ mName.setVisibility(View.VISIBLE);
+ if (animate) {
+ mName.setAlpha(0f);
+ mName.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FADE_NAME)
+ .setInterpolator(Interpolators.ALPHA_IN);
+ } else {
+ mName.setAlpha(1f);
+ }
+ } else {
+ if (animate) {
+ mName.setVisibility(View.VISIBLE);
+ mName.setAlpha(1f);
+ mName.animate()
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION_FADE_NAME)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(() -> {
+ mName.setVisibility(View.GONE);
+ mName.setAlpha(1f);
+ });
+ } else {
+ mName.setVisibility(View.GONE);
+ mName.setAlpha(1f);
+ }
+ }
+ setVisibility(View.VISIBLE);
+ setAlpha(1f);
+ } else {
+ // If item isn't shown, don't animate. The parent class will animate the view instead
+ setVisibility(View.GONE);
+ setAlpha(1f);
+ mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
+ mName.setAlpha(1f);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void setDarkAmount(float darkAmount) {
+ if (mDarkAmount == darkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ updateDark();
+ }
+
+ private void updateDark() {
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mName.setTextColor(blendedTextColor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
deleted file mode 100644
index 90f5577..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.FrameLayout;
-
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-
-import java.util.ArrayList;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-public class KeyguardUserSwitcher {
-
- private static final String TAG = "KeyguardUserSwitcher";
- private static final boolean ALWAYS_ON = false;
-
- private final Container mUserSwitcherContainer;
- private final KeyguardStatusBarView mStatusBarView;
- private final KeyguardUserAdapter mAdapter;
- private final AppearAnimationUtils mAppearAnimationUtils;
- private final KeyguardUserSwitcherScrim mBackground;
-
- private ViewGroup mUserSwitcher;
- private ObjectAnimator mBgAnimator;
- private UserSwitcherController mUserSwitcherController;
- private boolean mAnimating;
-
- public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView,
- NotificationPanelViewController panelViewController) {
- boolean keyguardUserSwitcherEnabled =
- context.getResources().getBoolean(
- com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
- UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
- if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
- mUserSwitcherContainer = (Container) userSwitcher.inflate();
- mBackground = new KeyguardUserSwitcherScrim(context);
- reinflateViews();
- mStatusBarView = statusBarView;
- mStatusBarView.setKeyguardUserSwitcher(this);
- panelViewController.setKeyguardUserSwitcher(this);
- mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
- mAdapter.registerDataSetObserver(mDataSetObserver);
- mUserSwitcherController = userSwitcherController;
- mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
- Interpolators.FAST_OUT_SLOW_IN);
- mUserSwitcherContainer.setKeyguardUserSwitcher(this);
- } else {
- mUserSwitcherContainer = null;
- mStatusBarView = null;
- mAdapter = null;
- mAppearAnimationUtils = null;
- mBackground = null;
- }
- }
-
- private void reinflateViews() {
- if (mUserSwitcher != null) {
- mUserSwitcher.setBackground(null);
- mUserSwitcher.removeOnLayoutChangeListener(mBackground);
- }
- mUserSwitcherContainer.removeAllViews();
-
- LayoutInflater.from(mUserSwitcherContainer.getContext())
- .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer);
-
- mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById(
- R.id.keyguard_user_switcher_inner);
- mUserSwitcher.addOnLayoutChangeListener(mBackground);
- mUserSwitcher.setBackground(mBackground);
- }
-
- public void setKeyguard(boolean keyguard, boolean animate) {
- if (mUserSwitcher != null) {
- if (keyguard && shouldExpandByDefault()) {
- show(animate);
- } else {
- hide(animate);
- }
- }
- }
-
- /**
- * @return true if the user switcher should be expanded by default on the lock screen.
- * @see android.os.UserManager#isUserSwitcherEnabled()
- */
- private boolean shouldExpandByDefault() {
- return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher();
- }
-
- public void show(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
- cancelAnimations();
- mAdapter.refresh();
- mUserSwitcherContainer.setVisibility(View.VISIBLE);
- mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
- if (animate) {
- startAppearAnimation();
- }
- }
- }
-
- private boolean hide(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
- cancelAnimations();
- if (animate) {
- startDisappearAnimation();
- } else {
- mUserSwitcherContainer.setVisibility(View.GONE);
- }
- mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
- return true;
- }
- return false;
- }
-
- private void cancelAnimations() {
- int count = mUserSwitcher.getChildCount();
- for (int i = 0; i < count; i++) {
- mUserSwitcher.getChildAt(i).animate().cancel();
- }
- if (mBgAnimator != null) {
- mBgAnimator.cancel();
- }
- mUserSwitcher.animate().cancel();
- mAnimating = false;
- }
-
- private void startAppearAnimation() {
- int count = mUserSwitcher.getChildCount();
- View[] objects = new View[count];
- for (int i = 0; i < count; i++) {
- objects[i] = mUserSwitcher.getChildAt(i);
- }
- mUserSwitcher.setClipChildren(false);
- mUserSwitcher.setClipToPadding(false);
- mAppearAnimationUtils.startAnimation(objects, new Runnable() {
- @Override
- public void run() {
- mUserSwitcher.setClipChildren(true);
- mUserSwitcher.setClipToPadding(true);
- }
- });
- mAnimating = true;
- mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
- mBgAnimator.setDuration(400);
- mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mBgAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBgAnimator = null;
- mAnimating = false;
- }
- });
- mBgAnimator.start();
- }
-
- private void startDisappearAnimation() {
- mAnimating = true;
- mUserSwitcher.animate()
- .alpha(0f)
- .setDuration(300)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mUserSwitcherContainer.setVisibility(View.GONE);
- mUserSwitcher.setAlpha(1f);
- mAnimating = false;
- }
- });
- }
-
- private void refresh() {
- final int childCount = mUserSwitcher.getChildCount();
- final int adapterCount = mAdapter.getCount();
- final int N = Math.max(childCount, adapterCount);
- for (int i = 0; i < N; i++) {
- if (i < adapterCount) {
- View oldView = null;
- if (i < childCount) {
- oldView = mUserSwitcher.getChildAt(i);
- }
- View newView = mAdapter.getView(i, oldView, mUserSwitcher);
- if (oldView == null) {
- // We ran out of existing views. Add it at the end.
- mUserSwitcher.addView(newView);
- } else if (oldView != newView) {
- // We couldn't rebind the view. Replace it.
- mUserSwitcher.removeViewAt(i);
- mUserSwitcher.addView(newView, i);
- }
- } else {
- int lastIndex = mUserSwitcher.getChildCount() - 1;
- mUserSwitcher.removeViewAt(lastIndex);
- }
- }
- }
-
- public boolean hideIfNotSimple(boolean animate) {
- if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
- return hide(animate);
- }
- return false;
- }
-
- boolean isAnimating() {
- return mAnimating;
- }
-
- public final DataSetObserver mDataSetObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- refresh();
- }
- };
-
- public void onDensityOrFontScaleChanged() {
- if (mUserSwitcherContainer != null) {
- reinflateViews();
- refresh();
- }
- }
-
- static class KeyguardUserAdapter extends
- UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
-
- private Context mContext;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private View mCurrentUserView;
- // List of users where the first entry is always the current user
- private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
-
- KeyguardUserAdapter(Context context, UserSwitcherController controller,
- KeyguardUserSwitcher kgu) {
- super(controller);
- mContext = context;
- mKeyguardUserSwitcher = kgu;
- }
-
- @Override
- public void notifyDataSetChanged() {
- refreshUserOrder();
- super.notifyDataSetChanged();
- }
-
- void refreshUserOrder() {
- ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
- mUsersOrdered = new ArrayList<>(users.size());
- for (int i = 0; i < users.size(); i++) {
- UserSwitcherController.UserRecord record = users.get(i);
- if (record.isCurrent) {
- mUsersOrdered.add(0, record);
- } else {
- mUsersOrdered.add(record);
- }
- }
- }
-
- @Override
- protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
- return mUsersOrdered;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- UserSwitcherController.UserRecord item = getItem(position);
- return createUserDetailItemView(convertView, parent, item);
- }
-
- KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
- if (!(convertView instanceof KeyguardUserDetailItemView)
- || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
- convertView = LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_user_switcher_item, parent, false);
- }
- return (KeyguardUserDetailItemView) convertView;
- }
-
- UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
- UserSwitcherController.UserRecord item) {
- KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
- if (!item.isCurrent || item.isGuest) {
- v.setOnClickListener(this);
- } else {
- v.setOnClickListener(null);
- v.setClickable(false);
- }
-
- String name = getName(mContext, item);
- if (item.picture == null) {
- v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
- } else {
- int avatarSize =
- (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
- Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
- drawable.setColorFilter(
- item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
- v.bind(name, drawable, item.info.id);
- }
- v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(item.isDisabledByAdmin);
- v.setEnabled(item.isSwitchToEnabled);
- v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
-
- if (item.isCurrent) {
- mCurrentUserView = v;
- }
- v.setTag(item);
- return v;
- }
-
- private static Drawable getDrawable(Context context,
- UserSwitcherController.UserRecord item) {
- Drawable drawable = getIconDrawable(context, item);
- int iconColorRes;
- if (item.isCurrent) {
- iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color;
- } else if (!item.isSwitchToEnabled) {
- iconColorRes = R.color.GM2_grey_600;
- } else {
- iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
- }
- drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme()));
-
- if (item.isCurrent) {
- Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected);
- drawable = new LayerDrawable(new Drawable[]{bg, drawable});
- }
-
- return drawable;
- }
-
- @Override
- public void onClick(View v) {
- UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
- if (user.isCurrent && !user.isGuest) {
- // Close the switcher if tapping the current user. Guest is excluded because
- // tapping the guest user while it's current clears the session.
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
- } else if (user.isSwitchToEnabled) {
- if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
- if (mCurrentUserView != null) {
- mCurrentUserView.setActivated(false);
- }
- v.setActivated(true);
- }
- onUserListItemClicked(user);
- }
- }
- }
-
- public static class Container extends FrameLayout {
-
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
-
- public Container(Context context, AttributeSet attrs) {
- super(context, attrs);
- setClipChildren(false);
- }
-
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- // Hide switcher if it didn't handle the touch event (and let the event go through).
- if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
- }
- return false;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
new file mode 100644
index 0000000..b76e451
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -0,0 +1,639 @@
+/*
+ * 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 static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
+
+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.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Interpolators;
+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.util.ViewController;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
+
+ private static final String TAG = "KeyguardUserSwitcherController";
+ 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 final UserSwitcherController mUserSwitcherController;
+ private final ScreenLifecycle mScreenLifecycle;
+ private final KeyguardUserAdapter mAdapter;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
+ private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+
+ // Child views of KeyguardUserSwitcherView
+ private KeyguardUserSwitcherListView mListView;
+ private LinearLayout mEndGuestButton;
+
+ // State info for the user switcher
+ private boolean mUserSwitcherOpen;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private boolean mCurrentUserIsGuest;
+ private int mBarState;
+ private float mDarkAmount;
+
+ private final KeyguardUpdateMonitorCallback mInfoCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+ // Any time the keyguard is hidden, try to close the user switcher menu to
+ // restore keyguard to the default state
+ if (!showing) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ }
+
+ @Override
+ public void onUserSwitching(int userId) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ };
+
+ private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+ @Override
+ public void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ };
+
+ 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;
+
+ if (mStatusBarStateController.goingToFullShade()
+ || mKeyguardStateController.isKeyguardFadingAway()) {
+ closeSwitcherIfOpenAndNotSimple(true);
+ }
+
+ setKeyguardUserSwitcherVisibility(
+ 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 KeyguardUserSwitcherController(
+ KeyguardUserSwitcherView keyguardUserSwitcherView,
+ Context context,
+ @Main Resources resources,
+ LayoutInflater layoutInflater,
+ ScreenLifecycle screenLifecycle,
+ UserSwitcherController userSwitcherController,
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DozeParameters dozeParameters) {
+ super(keyguardUserSwitcherView);
+ if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
+ mContext = context;
+ mScreenLifecycle = screenLifecycle;
+ mUserSwitcherController = userSwitcherController;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
+ mUserSwitcherController, this);
+ mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+ keyguardStateController, dozeParameters);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+
+ if (DEBUG) Log.d(TAG, "onInit");
+
+ mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
+ mEndGuestButton = mView.findViewById(R.id.end_guest_button);
+
+ mEndGuestButton.setOnClickListener(v -> {
+ mUserSwitcherController.showExitGuestDialog(mCurrentUserId);
+ });
+
+ mView.setOnTouchListener((v, event) -> {
+ if (!isListAnimating()) {
+ // Hide switcher if it didn't handle the touch event (and block the event from
+ // going through).
+ return closeSwitcherIfOpenAndNotSimple(true);
+ }
+ return false;
+ });
+ }
+
+ @Override
+ protected void onViewAttached() {
+ if (DEBUG) Log.d(TAG, "onViewAttached");
+ mAdapter.registerDataSetObserver(mDataSetObserver);
+ mDataSetObserver.onChanged();
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mScreenLifecycle.addObserver(mScreenObserver);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ if (DEBUG) Log.d(TAG, "onViewDetached");
+
+ // Detaching the view will always close the switcher
+ closeSwitcherIfOpenAndNotSimple(false);
+
+ mAdapter.unregisterDataSetObserver(mDataSetObserver);
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mScreenLifecycle.removeObserver(mScreenObserver);
+ }
+
+ /**
+ * See:
+ *
+ * <ul>
+ * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
+ * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
+ * </ul>
+ *
+ * @return true if the user switcher should be open by default on the lock screen.
+ * @see android.os.UserManager#isUserSwitcherEnabled()
+ */
+ public boolean isSimpleUserSwitcher() {
+ return mUserSwitcherController.isSimpleUserSwitcher();
+ }
+
+ /**
+ * @param animate if the transition should be animated
+ * @return true if the switcher state changed
+ */
+ public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
+ if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
+ setUserSwitcherOpened(false /* open */, animate);
+ return true;
+ }
+ return false;
+ }
+
+ public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ refreshUserList();
+ }
+ };
+
+ void refreshUserList() {
+ final int childCount = mListView.getChildCount();
+ final int adapterCount = mAdapter.getCount();
+ final int count = Math.max(childCount, adapterCount);
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
+ adapterCount));
+ }
+
+ boolean foundCurrentUser = false;
+ for (int i = 0; i < count; i++) {
+ if (i < adapterCount) {
+ View oldView = null;
+ if (i < childCount) {
+ oldView = mListView.getChildAt(i);
+ }
+ KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
+ mAdapter.getView(i, oldView, mListView);
+ UserSwitcherController.UserRecord userTag =
+ (UserSwitcherController.UserRecord) newView.getTag();
+ if (userTag.isCurrent) {
+ if (i != 0) {
+ Log.w(TAG, "Current user is not the first view in the list");
+ }
+ foundCurrentUser = true;
+ mCurrentUserId = userTag.info.id;
+ mCurrentUserIsGuest = userTag.isGuest;
+ // Current user is always visible
+ newView.updateVisibilities(true /* showItem */,
+ mUserSwitcherOpen /* showTextName */, false /* animate */);
+ } else {
+ // Views for non-current users are always expanded (e.g. they should the name
+ // next to the user icon). However, they could be hidden entirely if the list
+ // is closed.
+ newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
+ true /* showTextName */, false /* animate */);
+ }
+ newView.setDarkAmount(mDarkAmount);
+ if (oldView == null) {
+ // We ran out of existing views. Add it at the end.
+ mListView.addView(newView);
+ } else if (oldView != newView) {
+ // We couldn't rebind the view. Replace it.
+ mListView.replaceView(newView, i);
+ }
+ } else {
+ mListView.removeLastView();
+ }
+ }
+ if (!foundCurrentUser) {
+ Log.w(TAG, "Current user is not listed");
+ mCurrentUserId = UserHandle.USER_NULL;
+ mCurrentUserIsGuest = false;
+ }
+ }
+
+ /**
+ * Get the height of the keyguard user switcher view when closed.
+ */
+ public int getUserIconHeight() {
+ View firstChild = mListView.getChildAt(0);
+ return firstChild == null ? 0 : firstChild.getHeight();
+ }
+
+ /**
+ * Set the visibility of the keyguard user switcher view based on some new state.
+ */
+ public void setKeyguardUserSwitcherVisibility(
+ 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(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
+ animate);
+ PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+ ANIMATION_PROPERTIES, animate);
+ }
+
+ /**
+ * Set keyguard user switcher 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;
+ mListView.setDarkAmount(darkAmount);
+ mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+ if (!isAwake) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ }
+
+ private boolean isListAnimating() {
+ return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
+ }
+
+ /**
+ * Remove the callback if it exists.
+ */
+ public void removeCallback() {
+ if (DEBUG) Log.d(TAG, "removeCallback");
+ mKeyguardUserSwitcherCallback = null;
+ }
+
+ /**
+ * Register to receive notifications about keyguard user switcher state
+ * (see {@link KeyguardUserSwitcherListener}.
+ *
+ * Only one callback can be used at a time.
+ *
+ * @param callback The callback to register
+ */
+ public void setCallback(KeyguardUserSwitcherListener callback) {
+ if (DEBUG) Log.d(TAG, "setCallback");
+ mKeyguardUserSwitcherCallback = new WeakReference<>(callback);
+ }
+
+ /**
+ * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}.
+ * Switcher state is updatd before animations finish.
+ *
+ * @param animate true to animate transition. The user switcher state (i.e.
+ * {@link #isUserSwitcherOpen()}) is updated before animation is finished.
+ */
+ private void setUserSwitcherOpened(boolean open, boolean animate) {
+ boolean wasOpen = mUserSwitcherOpen;
+ if (DEBUG) {
+ Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen,
+ open, animate));
+ }
+ mUserSwitcherOpen = open;
+ if (mUserSwitcherOpen != wasOpen) {
+ notifyUserSwitcherStateChanged();
+ }
+ updateVisibilities(animate);
+ }
+
+ private void updateVisibilities(boolean animate) {
+ if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
+ mEndGuestButton.animate().cancel();
+ if (mUserSwitcherOpen && mCurrentUserIsGuest) {
+ // Show the "End guest session" button
+ mEndGuestButton.setVisibility(View.VISIBLE);
+ if (animate) {
+ mEndGuestButton.setAlpha(0f);
+ mEndGuestButton.animate()
+ .alpha(1f)
+ .setDuration(360)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .withEndAction(() -> {
+ mEndGuestButton.setClickable(true);
+ });
+ } else {
+ mEndGuestButton.setClickable(true);
+ mEndGuestButton.setAlpha(1f);
+ }
+ } else {
+ // Hide the "End guest session" button. If it's already GONE, don't try to
+ // animate it or it will appear again for an instant.
+ mEndGuestButton.setClickable(false);
+ if (animate && mEndGuestButton.getVisibility() != View.GONE) {
+ mEndGuestButton.setVisibility(View.VISIBLE);
+ mEndGuestButton.setAlpha(1f);
+ mEndGuestButton.animate()
+ .alpha(0f)
+ .setDuration(360)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(() -> {
+ mEndGuestButton.setVisibility(View.GONE);
+ mEndGuestButton.setAlpha(1f);
+ });
+ } else {
+ mEndGuestButton.setVisibility(View.GONE);
+ mEndGuestButton.setAlpha(1f);
+ }
+ }
+
+ mListView.updateVisibilities(mUserSwitcherOpen, animate);
+ }
+
+ private boolean isUserSwitcherOpen() {
+ return mUserSwitcherOpen;
+ }
+
+ private void notifyUserSwitcherStateChanged() {
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b",
+ mUserSwitcherOpen));
+ }
+ if (mKeyguardUserSwitcherCallback != null) {
+ KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get();
+ if (cb != null) {
+ cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen);
+ }
+ }
+ }
+
+ /**
+ * Callback for keyguard user switcher state information
+ */
+ public interface KeyguardUserSwitcherListener {
+
+ /**
+ * Called when the keyguard enters or leaves user switcher mode. This will be called
+ * before the animations are finished.
+ *
+ * @param open if true, keyguard is showing the user switcher or transitioning from/to user
+ * switcher mode.
+ */
+ void onKeyguardUserSwitcherChanged(boolean open);
+ }
+
+ static class KeyguardUserAdapter extends
+ UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final LayoutInflater mLayoutInflater;
+ private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+ private View mCurrentUserView;
+ // List of users where the first entry is always the current user
+ private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+
+ KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
+ UserSwitcherController controller,
+ KeyguardUserSwitcherController keyguardUserSwitcherController) {
+ super(controller);
+ mContext = context;
+ mResources = resources;
+ mLayoutInflater = layoutInflater;
+ mKeyguardUserSwitcherController = keyguardUserSwitcherController;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
+ // data set
+ refreshUserOrder();
+ super.notifyDataSetChanged();
+ }
+
+ void refreshUserOrder() {
+ ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+ mUsersOrdered = new ArrayList<>(users.size());
+ for (int i = 0; i < users.size(); i++) {
+ UserSwitcherController.UserRecord record = users.get(i);
+ if (record.isCurrent) {
+ mUsersOrdered.add(0, record);
+ } else {
+ mUsersOrdered.add(record);
+ }
+ }
+ }
+
+ @Override
+ protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+ return mUsersOrdered;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ UserSwitcherController.UserRecord item = getItem(position);
+ return createUserDetailItemView(convertView, parent, item);
+ }
+
+ @Override
+ public String getName(Context context, UserSwitcherController.UserRecord item) {
+ if (item.isGuest) {
+ return context.getString(com.android.settingslib.R.string.guest_nickname);
+ } else {
+ return super.getName(context, item);
+ }
+ }
+
+ KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+ if (!(convertView instanceof KeyguardUserDetailItemView)
+ || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+ convertView = mLayoutInflater.inflate(
+ R.layout.keyguard_user_switcher_item, parent, false);
+ }
+ return (KeyguardUserDetailItemView) convertView;
+ }
+
+ KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+ UserSwitcherController.UserRecord item) {
+ KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+ v.setOnClickListener(this);
+
+ String name = getName(mContext, item);
+ if (item.picture == null) {
+ v.bind(name, getDrawable(item).mutate(), item.resolveId());
+ } else {
+ int avatarSize =
+ (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+ Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
+ drawable.setColorFilter(
+ item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
+ v.bind(name, drawable, item.info.id);
+ }
+ v.setActivated(item.isCurrent);
+ v.setDisabledByAdmin(item.isDisabledByAdmin);
+ v.setEnabled(item.isSwitchToEnabled);
+ v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+
+ if (item.isCurrent) {
+ mCurrentUserView = v;
+ }
+ v.setTag(item);
+ return v;
+ }
+
+ private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+ Drawable drawable;
+ if (item.isCurrent && item.isGuest) {
+ drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+ } else {
+ drawable = 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;
+ }
+
+ @Override
+ public void onClick(View v) {
+ UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+
+ if (mKeyguardUserSwitcherController.isListAnimating()) {
+ return;
+ }
+
+ if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
+ if (user.isCurrent) {
+ // Close the switcher if tapping the current user
+ mKeyguardUserSwitcherController.setUserSwitcherOpened(
+ false /* open */, true /* animate */);
+ } else if (user.isSwitchToEnabled) {
+ if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
+ if (mCurrentUserView != null) {
+ mCurrentUserView.setActivated(false);
+ }
+ v.setActivated(true);
+ }
+ onUserListItemClicked(user);
+ }
+ } else {
+ // If switcher is closed, tapping anywhere in the view will open it
+ mKeyguardUserSwitcherController.setUserSwitcherOpened(
+ true /* open */, true /* animate */);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
new file mode 100644
index 0000000..7c82c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -0,0 +1,168 @@
+/*
+ * 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.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.keyguard.KeyguardConstants;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Interpolators;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
+
+ private static final String TAG = "KeyguardUserSwitcherListView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final int ANIMATION_DURATION_OPENING = 360;
+ private static final int ANIMATION_DURATION_CLOSING = 240;
+
+ private boolean mAnimating;
+ private final AppearAnimationUtils mAppearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtils;
+
+ public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setClipChildren(false);
+ mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING,
+ -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN);
+ mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING,
+ 0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN);
+ }
+
+ /**
+ * 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.
+ */
+ void setDarkAmount(float darkAmount) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (v instanceof KeyguardUserDetailItemView) {
+ ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
+ }
+ }
+ }
+
+ boolean isAnimating() {
+ return mAnimating;
+ }
+
+ /**
+ * Update visibilities of this view and child views for when the user list is open or closed.
+ * If closed, this hides everything but the first item (which is always the current user).
+ */
+ void updateVisibilities(boolean open, boolean animate) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
+ open, animate, getChildCount()));
+ }
+
+ mAnimating = false;
+
+ int userListCount = getChildCount();
+ if (userListCount > 0) {
+ // The first child is always the current user.
+ KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt(
+ 0));
+ currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */,
+ animate);
+ currentUserView.setClickable(true);
+ currentUserView.clearAnimation();
+ }
+
+ if (userListCount <= 1) {
+ return;
+ }
+
+ if (animate) {
+ // Create an array of all the remaining users (that aren't the current user).
+ KeyguardUserDetailItemView[] otherUserViews =
+ new KeyguardUserDetailItemView[userListCount - 1];
+ for (int i = 1, n = 0; i < userListCount; i++, n++) {
+ otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i);
+
+ // Update clickable state immediately so that the menu feels more responsive
+ otherUserViews[n].setClickable(open);
+
+ // Before running the animation, ensure visibility is set correctly
+ otherUserViews[n].updateVisibilities(
+ true /* showItem */, true /* showTextName */, false /* animate */);
+ otherUserViews[n].clearAnimation();
+ }
+
+ setClipChildren(false);
+ setClipToPadding(false);
+
+ mAnimating = true;
+
+ final int nonCurrentUserCount = otherUserViews.length;
+ if (open) {
+ mAppearAnimationUtils.startAnimation(otherUserViews, () -> {
+ setClipChildren(true);
+ setClipToPadding(true);
+ mAnimating = false;
+ });
+ } else {
+ mDisappearAnimationUtils.startAnimation(otherUserViews, () -> {
+ setClipChildren(true);
+ setClipToPadding(true);
+ for (int i = 0; i < nonCurrentUserCount; i++) {
+ otherUserViews[i].updateVisibilities(
+ false /* showItem */, true /* showTextName */, false /* animate */);
+ }
+ mAnimating = false;
+ });
+ }
+ } else {
+ for (int i = 1; i < userListCount; i++) {
+ KeyguardUserDetailItemView nonCurrentUserView =
+ ((KeyguardUserDetailItemView) getChildAt(i));
+ nonCurrentUserView.clearAnimation();
+ nonCurrentUserView.updateVisibilities(
+ open /* showItem */, true /* showTextName */, false /* animate */);
+ nonCurrentUserView.setClickable(open);
+ }
+ }
+ }
+
+ /**
+ * Replaces the view at the specified position in the group.
+ *
+ * @param index the position in the group of the view to remove
+ */
+ void replaceView(KeyguardUserDetailItemView newView, int index) {
+ removeViewAt(index);
+ addView(newView, index);
+ }
+
+ /**
+ * Removes the last view in the group.
+ */
+ void removeLastView() {
+ int lastIndex = getChildCount() - 1;
+ removeViewAt(lastIndex);
+ }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
index 14d57bf..3f0e23f 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
@@ -14,6 +14,18 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.statusbar.policy;
-parcelable ExternalTimeSuggestion;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherView extends FrameLayout {
+
+ public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 0fe338e..1ab7652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings.Global;
+import android.telephony.AccessNetworkConstants;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.ServiceState;
@@ -34,12 +35,18 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager.RegistrationCallback;
import android.text.Html;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.SignalIcon.MobileState;
import com.android.settingslib.Utils;
@@ -65,13 +72,19 @@
*/
public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> {
private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-
+ private static final int STATUS_HISTORY_SIZE = 64;
+ private static final int IMS_TYPE_WWAN = 1;
+ private static final int IMS_TYPE_WLAN = 2;
+ private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
private final TelephonyManager mPhone;
+ private final ImsMmTelManager mImsMmTelManager;
private final SubscriptionDefaults mDefaults;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
private final boolean mProviderModel;
+ private final Handler mReceiverHandler;
+ private int mImsType = IMS_TYPE_WWAN;
// Save entire info for logging, we only use the id.
final SubscriptionInfo mSubscriptionInfo;
// @VisibleForDemoMode
@@ -86,16 +99,21 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
private ServiceState mServiceState;
private SignalStrength mSignalStrength;
+ private int mLastLevel;
private MobileIconGroup mDefaultIcons;
private Config mConfig;
@VisibleForTesting
boolean mInflateSignalStrengths = false;
private MobileStatusTracker.Callback mCallback;
+ private RegistrationCallback mRegistrationCallback;
+ private int mLastWwanLevel;
+ private int mLastWlanLevel;
+ private int mLastWlanCrossSimLevel;
@VisibleForTesting
MobileStatusTracker mMobileStatusTracker;
- // Save the previous HISTORY_SIZE states for logging.
- private final String[] mMobileStatusHistory = new String[HISTORY_SIZE];
+ // Save the previous STATUS_HISTORY_SIZE states for logging.
+ private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE];
// Where to copy the next state into.
private int mMobileStatusHistoryIndex;
@@ -116,6 +134,7 @@
.toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
+ mReceiverHandler = new Handler(receiverLooper);
mNetworkToIconLookup = mapIconSets(mConfig);
mDefaultIcons = getDefaultIcons(mConfig);
@@ -133,6 +152,8 @@
}
};
mCallback = new MobileStatusTracker.Callback() {
+ private String mLastStatus;
+
@Override
public void onMobileStatusChanged(boolean updateTelephony,
MobileStatus mobileStatus) {
@@ -141,11 +162,15 @@
+ " updateTelephony=" + updateTelephony
+ " mobileStatus=" + mobileStatus.toString());
}
- String status = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append(mobileStatus.toString())
- .toString();
- recordLastMobileStatus(status);
+ String currentStatus = mobileStatus.toString();
+ if (!currentStatus.equals(mLastStatus)) {
+ mLastStatus = currentStatus;
+ String status = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append(currentStatus)
+ .toString();
+ recordLastMobileStatus(status);
+ }
updateMobileStatus(mobileStatus);
if (updateTelephony) {
updateTelephony();
@@ -154,6 +179,53 @@
}
}
};
+
+ mRegistrationCallback = new RegistrationCallback() {
+ @Override
+ public void onRegistered(ImsRegistrationAttributes attributes) {
+ Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
+ int imsTransportType = attributes.getTransportType();
+ int registrationAttributes = attributes.getAttributeFlags();
+ if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+ if (registrationAttributes == 0) {
+ mImsType = IMS_TYPE_WLAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+ getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (registrationAttributes
+ == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
+ mImsType = IMS_TYPE_WLAN_CROSS_SIM;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+ getCallStrengthDescription(
+ mLastWlanCrossSimLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+ }
+
+ @Override
+ public void onUnregistered(ImsReasonInfo info) {
+ Log.d(mTag, "onDeregistered: " + "info=" + info);
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ };
+ mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
info, mDefaults, mCallback);
mProviderModel = FeatureFlagUtils.isEnabled(
@@ -202,14 +274,41 @@
mContext.getContentResolver().registerContentObserver(Global.getUriFor(
Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()),
true, mObserver);
+ if (mProviderModel) {
+ mReceiverHandler.post(mTryRegisterIms);
+ }
}
+ // There is no listener to monitor whether the IMS service is ready, so we have to retry the
+ // IMS registration.
+ private final Runnable mTryRegisterIms = new Runnable() {
+ private static final int MAX_RETRY = 12;
+ private int mRetryCount;
+
+ @Override
+ public void run() {
+ try {
+ mRetryCount++;
+ mImsMmTelManager.registerImsRegistrationCallback(
+ mReceiverHandler::post, mRegistrationCallback);
+ Log.d(mTag, "registerImsRegistrationCallback succeeded");
+ } catch (RuntimeException | ImsException e) {
+ if (mRetryCount < MAX_RETRY) {
+ Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e);
+ // Wait for 5 seconds to retry
+ mReceiverHandler.postDelayed(mTryRegisterIms, 5000);
+ }
+ }
+ }
+ };
+
/**
* Stop listening for phone state changes.
*/
public void unregisterListener() {
mMobileStatusTracker.setListening(false);
mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback);
}
private void updateInflateSignalStrength() {
@@ -452,9 +551,9 @@
/**
* Extracts the CellSignalStrengthCdma from SignalStrength then returns the level
*/
- private final int getCdmaLevel() {
+ private int getCdmaLevel(SignalStrength signalStrength) {
List<CellSignalStrengthCdma> signalStrengthCdma =
- mSignalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
+ signalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
if (!signalStrengthCdma.isEmpty()) {
return signalStrengthCdma.get(0).getLevel();
}
@@ -467,6 +566,7 @@
mCurrentState.dataSim = mobileStatus.dataSim;
mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode;
mDataState = mobileStatus.dataState;
+ notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
mSignalStrength = mobileStatus.signalStrength;
mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
@@ -481,9 +581,117 @@
&& (lastVoiceState == -1
|| (lastVoiceState == ServiceState.STATE_IN_SERVICE
|| currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
- notifyNoCallingStatusChange(
- currentVoiceState != ServiceState.STATE_IN_SERVICE,
- mSubscriptionInfo.getSubscriptionId());
+ boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+ IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+ getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+
+ private int getCallStrengthIcon(int level, boolean isWifi) {
+ return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
+ : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
+ }
+
+ private String getCallStrengthDescription(int level, boolean isWifi) {
+ return isWifi
+ ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level])
+ .toString()
+ : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level])
+ .toString();
+ }
+
+ void refreshCallIndicator(SignalCallback callback) {
+ boolean isNoCalling = mServiceState != null
+ && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
+ IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+ getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+ callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+
+ switch (mImsType) {
+ case IMS_TYPE_WWAN:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ break;
+ case IMS_TYPE_WLAN:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+ getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+ break;
+ case IMS_TYPE_WLAN_CROSS_SIM:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false));
+ }
+ callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyWifiLevelChange(int level) {
+ if (!mProviderModel) {
+ return;
+ }
+ Log.d("mTag", "notifyWifiLevelChange " + mImsType);
+ mLastWlanLevel = level;
+ if (mImsType != IMS_TYPE_WLAN) {
+ return;
+ }
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(level, /* isWifi= */true),
+ getCallStrengthDescription(level, /* isWifi= */true));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyDefaultMobileLevelChange(int level) {
+ if (!mProviderModel) {
+ return;
+ }
+ Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType);
+ mLastWlanCrossSimLevel = level;
+ if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
+ return;
+ }
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(level, /* isWifi= */false),
+ getCallStrengthDescription(level, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
+ if (!mProviderModel) {
+ return;
+ }
+ int newLevel = getSignalLevel(signalStrength);
+ if (newLevel != mLastLevel) {
+ mLastLevel = newLevel;
+ Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType);
+ mLastWwanLevel = newLevel;
+ if (mImsType == IMS_TYPE_WWAN) {
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(newLevel, /* isWifi= */false),
+ getCallStrengthDescription(newLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ if (mCurrentState.dataSim) {
+ mNetworkController.notifyDefaultMobileLevelChange(newLevel);
+ }
+ }
+ }
+
+ int getSignalLevel(SignalStrength signalStrength) {
+ if (signalStrength == null) {
+ return 0;
+ }
+ if (!signalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+ return getCdmaLevel(signalStrength);
+ } else {
+ return signalStrength.getLevel();
}
}
@@ -501,11 +709,7 @@
checkDefaultData();
mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null;
if (mCurrentState.connected) {
- if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
- mCurrentState.level = getCdmaLevel();
- } else {
- mCurrentState.level = mSignalStrength.getLevel();
- }
+ mCurrentState.level = getSignalLevel(mSignalStrength);
}
String iconKey = getIconKey(mTelephonyDisplayInfo);
@@ -577,7 +781,13 @@
}
private void recordLastMobileStatus(String mobileStatus) {
- mMobileStatusHistory[mMobileStatusHistoryIndex++ & (HISTORY_SIZE - 1)] = mobileStatus;
+ mMobileStatusHistory[mMobileStatusHistoryIndex] = mobileStatus;
+ mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE;
+ }
+
+ @VisibleForTesting
+ void setImsType(int imsType) {
+ mImsType = imsType;
}
@Override
@@ -592,15 +802,17 @@
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
pw.println(" MobileStatusHistory");
int size = 0;
- for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mMobileStatusHistory[i] != null) size++;
+ for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
+ if (mMobileStatusHistory[i] != null) {
+ size++;
+ }
}
// Print out the previous states in ordered number.
- for (int i = mMobileStatusHistoryIndex + HISTORY_SIZE - 1;
- i >= mMobileStatusHistoryIndex + HISTORY_SIZE - size; i--) {
+ for (int i = mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - 1;
+ i >= mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - size; i--) {
pw.println(" Previous MobileStatus("
- + (mMobileStatusHistoryIndex + HISTORY_SIZE - i) + "): "
- + mMobileStatusHistory[i & (HISTORY_SIZE - 1)]);
+ + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
+ + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index e60d5c5..0a9fead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -53,7 +53,7 @@
/**
* Callback for listeners to be able to update the state of any UI tracking connectivity
- * @param statusIcon the icon that should be shown in the status bar
+ * @param statusIcon the icon that should be shown in the status bar
* @param qsIcon the icon to show in Quick Settings
* @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar
* @param qsType similar to above, the resId of the data type icon to show in Quick Settings
@@ -95,11 +95,11 @@
boolean noNetworksAvailable) {}
/**
- * Callback for listeners to be able to update the no calling & SMS status
- * @param noCalling whether the calling and SMS is not working.
+ * Callback for listeners to be able to update the call indicator
+ * @param statusIcon the icon for the call indicator
* @param subId subscription ID for which to update the UI
*/
- default void setNoCallingStatus(boolean noCalling, int subId) {}
+ default void setCallIndicator(IconState statusIcon, int subId) {}
}
public interface EmergencyListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 80c7811..9f92142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -554,6 +554,20 @@
return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
}
+ void notifyWifiLevelChange(int level) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+ mobileSignalController.notifyWifiLevelChange(level);
+ }
+ }
+
+ void notifyDefaultMobileLevelChange(int level) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+ mobileSignalController.notifyDefaultMobileLevelChange(level);
+ }
+ }
+
private void notifyControllersMobileDataChanged() {
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
@@ -623,6 +637,9 @@
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
+ if (mProviderModel) {
+ mobileSignalController.refreshCallIndicator(cb);
+ }
}
mCallbackHandler.setListening(cb, true);
}
@@ -1272,7 +1289,8 @@
}
private void recordLastNetworkCallback(String callback) {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+ mHistory[mHistoryIndex] = callback;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 554145e..4b6722c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -23,6 +23,7 @@
import com.android.settingslib.SignalIcon.IconGroup;
import com.android.settingslib.SignalIcon.State;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.io.PrintWriter;
@@ -167,8 +168,8 @@
}
}
- protected final void notifyNoCallingStatusChange(boolean noCalling, int subId) {
- mCallbackHandler.setNoCallingStatus(noCalling, subId);
+ protected final void notifyCallStateChange(IconState statusIcon, int subId) {
+ mCallbackHandler.setCallIndicator(statusIcon, subId);
}
/**
@@ -187,7 +188,8 @@
* and last value of any state data.
*/
protected void recordLastState() {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+ mHistory[mHistoryIndex].copyFrom(mLastState);
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
public void dump(PrintWriter pw) {
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 68d74ef..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);
}
@@ -423,7 +425,7 @@
}
}
- private void showExitGuestDialog(int id) {
+ protected void showExitGuestDialog(int id) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -680,11 +682,7 @@
if (item.isAddUser) {
iconRes = R.drawable.ic_add_circle;
} else if (item.isGuest) {
- if (item.isCurrent) {
- iconRes = R.drawable.ic_exit_to_app;
- } else {
- iconRes = R.drawable.ic_avatar_guest_user;
- }
+ iconRes = R.drawable.ic_avatar_guest_user;
} else {
iconRes = R.drawable.ic_avatar_user;
}
@@ -785,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);
@@ -798,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/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 1fd2ccb..16998d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.io.PrintWriter;
import java.util.Objects;
public class WifiSignalController extends
@@ -202,6 +203,7 @@
mCurrentState.connected = mWifiTracker.connected;
mCurrentState.ssid = mWifiTracker.ssid;
mCurrentState.rssi = mWifiTracker.rssi;
+ notifyWifiLevelChangeIfNecessary(mWifiTracker.level);
mCurrentState.level = mWifiTracker.level;
mCurrentState.statusLabel = mWifiTracker.statusLabel;
mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
@@ -211,6 +213,12 @@
: mUnmergedWifiIconGroup;
}
+ void notifyWifiLevelChangeIfNecessary(int level) {
+ if (level != mCurrentState.level) {
+ mNetworkController.notifyWifiLevelChange(level);
+ }
+ }
+
boolean isCarrierMergedWifi(int subId) {
return mCurrentState.isDefault
&& mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
@@ -225,6 +233,12 @@
notifyListenersIfNecessary();
}
+ @Override
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ mWifiTracker.dump(pw);
+ }
+
/**
* Handler to receive the data activity on wifi.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..9e78a66 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,12 +15,15 @@
*/
package com.android.systemui.theme;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
-import android.os.SystemProperties;
+import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -50,13 +53,6 @@
public class ThemeOverlayApplier implements Dumpable {
private static final String TAG = "ThemeOverlayApplier";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean MONET_ENABLED = SystemProperties
- .getBoolean("persist.sysui.monet", false);
-
- @VisibleForTesting
- static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
- @VisibleForTesting
- static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
@VisibleForTesting
static final String ANDROID_PACKAGE = "android";
@@ -65,10 +61,8 @@
@VisibleForTesting
static final String SYSUI_PACKAGE = "com.android.systemui";
- @VisibleForTesting
static final String OVERLAY_CATEGORY_ACCENT_COLOR =
"android.theme.customization.accent_color";
- @VisibleForTesting
static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
"android.theme.customization.system_palette";
@VisibleForTesting
@@ -117,16 +111,6 @@
OVERLAY_CATEGORY_ICON_ANDROID,
OVERLAY_CATEGORY_ICON_SYSUI);
- /**
- * List of main colors of Monet themes. These are extracted from overlays installed
- * on the system.
- */
- private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
- /**
- * Same as above, but providing accent colors instead of a system palette.
- */
- private final ArrayList<Integer> mAccentColors = new ArrayList<>();
-
/* Allowed overlay categories for each target package. */
private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
/* Target package for each overlay category. */
@@ -162,64 +146,17 @@
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
- collectMonetSystemOverlays();
dumpManager.registerDumpable(TAG, this);
}
/**
- * List of accent colors available as Monet overlays.
- */
- List<Integer> getAvailableAccentColors() {
- return mAccentColors;
- }
-
- /**
- * List of main system colors available as Monet overlays.
- */
- List<Integer> getAvailableSystemColors() {
- return mMainSystemColors;
- }
-
- private void collectMonetSystemOverlays() {
- if (!MONET_ENABLED) {
- return;
- }
- List<OverlayInfo> androidOverlays = mOverlayManager
- .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
- for (OverlayInfo overlayInfo : androidOverlays) {
- String packageName = overlayInfo.packageName;
- if (DEBUG) {
- Log.d(TAG, "Processing overlay " + packageName);
- }
- if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
- && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
- try {
- String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
- mMainSystemColors.add(Integer.parseInt(color, 16));
- } catch (NumberFormatException e) {
- Log.w(TAG, "Invalid package name for overlay " + packageName, e);
- }
- } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
- && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
- try {
- String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
- mAccentColors.add(Integer.parseInt(color, 16));
- } catch (NumberFormatException e) {
- Log.w(TAG, "Invalid package name for overlay " + packageName, e);
- }
- } else if (DEBUG) {
- Log.d(TAG, "Unknown overlay: " + packageName + " category: "
- + overlayInfo.category);
- }
- }
- }
-
- /**
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
*/
void applyCurrentUserOverlays(
- Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
+ Map<String, OverlayIdentifier> categoryToPackage,
+ FabricatedOverlay[] pendingCreation,
+ Set<UserHandle> userHandles) {
// Disable all overlays that have not been specified in the user setting.
final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
@@ -229,55 +166,68 @@
final List<OverlayInfo> overlays = new ArrayList<>();
targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
.getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
- final Map<String, String> overlaysToDisable = overlays.stream()
+ final List<Pair<String, String>> overlaysToDisable = overlays.stream()
.filter(o ->
mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
.filter(o -> overlayCategoriesToDisable.contains(o.category))
.filter(o -> o.isEnabled())
- .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
+ .map(o -> new Pair<>(o.category, o.packageName))
+ .collect(Collectors.toList());
+
+ OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+ if (pendingCreation != null) {
+ for (FabricatedOverlay overlay : pendingCreation) {
+ transaction.registerFabricatedOverlay(overlay);
+ }
+ }
// Toggle overlays in the order of THEME_CATEGORIES.
for (String category : THEME_CATEGORIES) {
if (categoryToPackage.containsKey(category)) {
- setEnabled(categoryToPackage.get(category), category, userHandles, true);
- } else if (overlaysToDisable.containsKey(category)) {
- setEnabled(overlaysToDisable.get(category), category, userHandles, false);
+ OverlayIdentifier overlayInfo = categoryToPackage.get(category);
+ setEnabled(transaction, overlayInfo, category, userHandles, true);
}
}
- }
-
- private void setEnabled(
- String packageName, String category, Set<UserHandle> handles, boolean enabled) {
- for (UserHandle userHandle : handles) {
- setEnabledAsync(packageName, userHandle, enabled);
+ for (Pair<String, String> packageToDisable : overlaysToDisable) {
+ OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+ setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
}
- if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
- setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
- }
- }
- private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
mExecutor.execute(() -> {
- if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
try {
- if (enabled) {
- mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
- } else {
- mOverlayManager.setEnabled(pkg, false, userHandle);
- }
+ mOverlayManager.commit(transaction.build());
} catch (SecurityException | IllegalStateException e) {
- Log.e(TAG,
- String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
+ Log.e(TAG, "setEnabled failed", e);
}
});
}
+ @VisibleForTesting
+ protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+ return new OverlayManagerTransaction.Builder();
+ }
+
+ private void setEnabled(OverlayManagerTransaction.Builder transaction,
+ OverlayIdentifier identifier, String category, Set<UserHandle> handles,
+ boolean enabled) {
+ if (DEBUG) {
+ Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
+ + category + ": " + enabled);
+ }
+ for (UserHandle userHandle : handles) {
+ transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
+ }
+ if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
+ transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
+ }
+ }
+
/**
* @inherit
*/
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("mMainSystemColors=" + mMainSystemColors.size());
- pw.println("mAccentColors=" + mAccentColors.size());
+ pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
+ pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d9f4744..522a42b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
@@ -25,6 +26,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Color;
@@ -40,7 +43,6 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -48,6 +50,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
@@ -59,10 +62,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -77,9 +80,12 @@
*/
@SysUISingleton
public class ThemeOverlayController extends SystemUI implements Dumpable {
- private static final String TAG = "ThemeOverlayController";
+ protected static final String TAG = "ThemeOverlayController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected static final int MAIN = 0;
+ protected static final int ACCENT = 1;
+
// If lock screen wallpaper colors should also be considered when selecting the theme.
// Doing this has performance impact, given that overlays would need to be swapped when
// the device unlocks.
@@ -95,16 +101,19 @@
private final Handler mBgHandler;
private final WallpaperManager mWallpaperManager;
private final KeyguardStateController mKeyguardStateController;
+ private final boolean mIsMonetEnabled;
private WallpaperColors mLockColors;
private WallpaperColors mSystemColors;
- // Color extracted from wallpaper, NOT the color used on the overlay
+ // If fabricated overlays were already created for the current theme.
+ private boolean mNeedsOverlayCreation;
+ // Dominant olor extracted from wallpaper, NOT the color used on the overlay
protected int mMainWallpaperColor = Color.TRANSPARENT;
- // Color extracted from wallpaper, NOT the color used on the overlay
+ // Accent color extracted from wallpaper, NOT the color used on the overlay
protected int mWallpaperAccentColor = Color.TRANSPARENT;
- // Main system color that maps to an overlay color
- private int mSystemOverlayColor = Color.TRANSPARENT;
- // Accent color that maps to an overlay color
- private int mAccentOverlayColor = Color.TRANSPARENT;
+ // System colors overlay
+ private FabricatedOverlay mSystemOverlay;
+ // Accent colors overlay
+ private FabricatedOverlay mAccentOverlay;
@Inject
public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -112,9 +121,10 @@
@Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
SecureSettings secureSettings, WallpaperManager wallpaperManager,
UserManager userManager, KeyguardStateController keyguardStateController,
- DumpManager dumpManager) {
+ DumpManager dumpManager, FeatureFlags featureFlags) {
super(context);
+ mIsMonetEnabled = featureFlags.isMonetEnabled();
mBroadcastDispatcher = broadcastDispatcher;
mUserManager = userManager;
mBgExecutor = bgExecutor;
@@ -221,20 +231,16 @@
mMainWallpaperColor = mainColor;
mWallpaperAccentColor = accentCandidate;
- // Let's compare these colors to our finite set of overlays, and then pick an overlay.
- List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
- List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
-
- if (systemColors.size() == 0 || accentColors.size() == 0) {
+ if (mIsMonetEnabled) {
+ mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
+ mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+ mNeedsOverlayCreation = true;
if (DEBUG) {
- Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+ Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
+ + mAccentOverlay);
}
- return;
}
- mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
- mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
-
updateThemeOverlays();
}
@@ -257,42 +263,10 @@
}
/**
- * Given a color and a list of candidates, return the candidate that's the most similar to the
- * given color.
+ * Given a color candidate, return an overlay definition.
*/
- protected int getClosest(List<Integer> candidates, int color) {
- float[] hslMain = new float[3];
- float[] hslCandidate = new float[3];
-
- ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
- hslMain[0] /= 360f;
-
- // To close to white or black, let's use the default system theme instead of
- // applying a colorized one.
- if (hslMain[2] < 0.05 || hslMain[2] > 0.95) {
- return Color.TRANSPARENT;
- }
-
- float minDistance = Float.MAX_VALUE;
- int closestColor = Color.TRANSPARENT;
- for (int candidate: candidates) {
- ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
- hslCandidate);
- hslCandidate[0] /= 360f;
-
- float sqDistance = squared(hslCandidate[0] - hslMain[0])
- + squared(hslCandidate[1] - hslMain[1])
- + squared(hslCandidate[2] - hslMain[2]);
- if (sqDistance < minDistance) {
- minDistance = sqDistance;
- closestColor = candidate;
- }
- }
- return closestColor;
- }
-
- private static float squared(float f) {
- return f * f;
+ protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
+ return null;
}
private void updateThemeOverlays() {
@@ -301,20 +275,15 @@
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
currentUser);
if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
- boolean hasSystemPalette = false;
- boolean hasAccentColor = false;
- final Map<String, String> categoryToPackage = new ArrayMap<>();
+ final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
if (!TextUtils.isEmpty(overlayPackageJson)) {
try {
JSONObject object = new JSONObject(overlayPackageJson);
for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
if (object.has(category)) {
- if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
- hasAccentColor = true;
- } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
- hasSystemPalette = true;
- }
- categoryToPackage.put(category, object.getString(category));
+ OverlayIdentifier identifier =
+ new OverlayIdentifier(object.getString(category));
+ categoryToPackage.put(category, identifier);
}
}
} catch (JSONException e) {
@@ -322,17 +291,41 @@
}
}
- // Let's apply the system palette, but only if it was not overridden by the style picker.
- if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
- categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
- ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
- + getColorString(mSystemOverlayColor));
+ // Let's generate system overlay if the style picker decided to override it.
+ OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
+ try {
+ int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
+ mSystemOverlay = getOverlay(color, MAIN);
+ mNeedsOverlayCreation = true;
+ categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName());
+ }
}
- // Same for the accent color
- if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
- categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
- ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
- + getColorString(mAccentOverlayColor));
+
+ // Same for accent color.
+ OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
+ if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
+ try {
+ int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
+ mAccentOverlay = getOverlay(color, ACCENT);
+ mNeedsOverlayCreation = true;
+ categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName());
+ }
+ }
+
+ // Compatibility with legacy themes, where full packages were defined, instead of just
+ // colors.
+ if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
+ && mSystemOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+ }
+ if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
+ && mAccentOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
}
Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -341,28 +334,31 @@
userHandles.add(userInfo.getUserHandle());
}
}
- mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
- }
-
- private String getColorString(int color) {
- String colorString = Integer.toHexString(color).toUpperCase();
- while (colorString.length() < 6) {
- colorString = "0" + colorString;
+ if (DEBUG) {
+ Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
+ .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
+ Collectors.joining(", ")));
}
- // Remove alpha component
- if (colorString.length() > 6) {
- colorString = colorString.substring(colorString.length() - 6);
+ if (mNeedsOverlayCreation) {
+ mNeedsOverlayCreation = false;
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+ mSystemOverlay, mAccentOverlay
+ }, userHandles);
+ } else {
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
}
- return colorString;
}
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
pw.println("mLockColors=" + mLockColors);
pw.println("mSystemColors=" + mSystemColors);
pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
- pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
- pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+ pw.println("mSystemOverlayColor=" + mSystemOverlay);
+ pw.println("mAccentOverlayColor=" + mAccentOverlay);
+ pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+ pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index e9fcf1a..365cd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -20,10 +20,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.ToastPresenter;
import com.android.internal.R;
+import com.android.launcher3.icons.IconFactory;
import com.android.systemui.plugins.ToastPlugin;
/**
@@ -35,23 +46,43 @@
final CharSequence mText;
final ToastPlugin.Toast mPluginToast;
- final int mDefaultGravity;
- final int mDefaultY;
+ private final String mPackageName;
+ private final int mUserId;
+ private final LayoutInflater mLayoutInflater;
+ private final boolean mToastStyleEnabled;
+
final int mDefaultX = 0;
final int mDefaultHorizontalMargin = 0;
final int mDefaultVerticalMargin = 0;
- SystemUIToast(Context context, CharSequence text) {
- this(context, text, null);
+ private int mDefaultY;
+ private int mDefaultGravity;
+
+ @NonNull private final View mToastView;
+ @Nullable private final Animator mInAnimator;
+ @Nullable private final Animator mOutAnimator;
+
+ SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+ String packageName, int userId, boolean toastStyleEnabled, int orientation) {
+ this(layoutInflater, context, text, null, packageName, userId,
+ toastStyleEnabled, orientation);
}
- SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+ SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+ ToastPlugin.Toast pluginToast, String packageName, int userId,
+ boolean toastStyleEnabled, int orientation) {
+ mToastStyleEnabled = toastStyleEnabled;
+ mLayoutInflater = layoutInflater;
mContext = context;
mText = text;
mPluginToast = pluginToast;
+ mPackageName = packageName;
+ mUserId = userId;
+ mToastView = inflateToastView();
+ mInAnimator = createInAnimator();
+ mOutAnimator = createOutAnimator();
- mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
- mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+ onOrientationChange(orientation);
}
@Override
@@ -102,28 +133,19 @@
@Override
@NonNull
public View getView() {
- if (isPluginToast() && mPluginToast.getView() != null) {
- return mPluginToast.getView();
- }
- return ToastPresenter.getTextToastView(mContext, mText);
+ return mToastView;
}
@Override
@Nullable
public Animator getInAnimation() {
- if (isPluginToast() && mPluginToast.getInAnimation() != null) {
- return mPluginToast.getInAnimation();
- }
- return null;
+ return mInAnimator;
}
@Override
@Nullable
public Animator getOutAnimation() {
- if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
- return mPluginToast.getOutAnimation();
- }
- return null;
+ return mOutAnimator;
}
/**
@@ -136,4 +158,80 @@
private boolean isPluginToast() {
return mPluginToast != null;
}
+
+ private View inflateToastView() {
+ if (isPluginToast() && mPluginToast.getView() != null) {
+ return mPluginToast.getView();
+ }
+
+ View toastView;
+ if (mToastStyleEnabled) {
+ toastView = mLayoutInflater.inflate(
+ com.android.systemui.R.layout.text_toast, null);
+ ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
+
+ ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
+ .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId));
+ } else {
+ toastView = ToastPresenter.getTextToastView(mContext, mText);
+ }
+
+ return toastView;
+ }
+
+ /**
+ * Called on orientation changes to update parameters associated with the toast placement.
+ */
+ public void onOrientationChange(int orientation) {
+ if (mPluginToast != null) {
+ mPluginToast.onOrientationChange(orientation);
+ }
+
+ mDefaultY = mContext.getResources().getDimensionPixelSize(
+ mToastStyleEnabled
+ ? com.android.systemui.R.dimen.toast_y_offset
+ : R.dimen.toast_y_offset);
+ mDefaultGravity =
+ mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+ }
+
+ private Animator createInAnimator() {
+ if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+ return mPluginToast.getInAnimation();
+ }
+
+ return mToastStyleEnabled
+ ? ToastDefaultAnimation.Companion.toastIn(getView())
+ : null;
+ }
+
+ private Animator createOutAnimator() {
+ if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+ return mPluginToast.getOutAnimation();
+ }
+ return mToastStyleEnabled
+ ? ToastDefaultAnimation.Companion.toastOut(getView())
+ : null;
+ }
+
+ /**
+ * Get badged app icon if necessary, similar as used in the Settings UI.
+ * @return The icon to use
+ */
+ public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
+ int userId) {
+ final PackageManager packageManager = context.getPackageManager();
+ try {
+ final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+ IconFactory iconFactory = IconFactory.obtain(context);
+ Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+ appInfo.loadUnbadgedIcon(packageManager), user, false).icon;
+ return new BitmapDrawable(context.getResources(), iconBmp);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e);
+ return null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
new file mode 100644
index 0000000..603d690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.toast
+
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.animation.AnimatorSet
+
+class ToastDefaultAnimation {
+ /**
+ * sum of the in and out animation durations cannot exceed
+ * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast
+ * window from being removed before animations are completed
+ */
+ companion object {
+ // total duration shouldn't exceed NotificationManagerService's delay for "in" animation
+ fun toastIn(view: View): AnimatorSet? {
+ val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+ val text: View? = view.findViewById(com.android.systemui.R.id.text)
+ if (icon == null || text == null) {
+ return null
+ }
+ val linearInterp = LinearInterpolator()
+ val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f)
+ val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply {
+ interpolator = scaleInterp
+ duration = 333
+ }
+ val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply {
+ interpolator = scaleInterp
+ duration = 333
+ }
+ val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 66
+ }
+ text.alpha = 0f // Set now otherwise won't apply until start delay
+ val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 283
+ startDelay = 50
+ }
+ icon.alpha = 0f // Set now otherwise won't apply until start delay
+ val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 283
+ startDelay = 50
+ }
+ return AnimatorSet().apply {
+ playTogether(sX, sY, vA, tA, iA)
+ }
+ }
+
+ fun toastOut(view: View): AnimatorSet? {
+ // total duration shouldn't exceed NotificationManagerService's delay for "out" anim
+ val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+ val text: View? = view.findViewById(com.android.systemui.R.id.text)
+ if (icon == null || text == null) {
+ return null
+ }
+ val linearInterp = LinearInterpolator()
+ val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f)
+ val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply {
+ interpolator = scaleInterp
+ duration = 250
+ }
+ val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply {
+ interpolator = scaleInterp
+ duration = 250
+ }
+ val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 100
+ startDelay = 150
+ }
+ val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 166
+ }
+ val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 166
+ }
+ return AnimatorSet().apply {
+ playTogether(sX, sY, vA, tA, iA)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index d8cb61c..8b782d4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -17,6 +17,7 @@
package com.android.systemui.toast;
import android.content.Context;
+import android.view.LayoutInflater;
import androidx.annotation.NonNull;
@@ -26,6 +27,7 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.ToastPlugin;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -40,10 +42,18 @@
public class ToastFactory implements Dumpable {
// only one ToastPlugin can be connected at a time.
private ToastPlugin mPlugin;
+ private final LayoutInflater mLayoutInflater;
+ private final boolean mToastStyleEnabled;
@Inject
- public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+ public ToastFactory(
+ LayoutInflater layoutInflater,
+ PluginManager pluginManager,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
+ mLayoutInflater = layoutInflater;
dumpManager.registerDumpable("ToastFactory", this);
+ mToastStyleEnabled = featureFlags.isToastStyleEnabled();
pluginManager.addPluginListener(
new PluginListener<ToastPlugin>() {
@Override
@@ -64,11 +74,13 @@
* Create a toast to be shown by ToastUI.
*/
public SystemUIToast createToast(Context context, CharSequence text, String packageName,
- int userId) {
+ int userId, int orientation) {
if (isPluginAvailable()) {
- return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+ return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+ packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
}
- return new SystemUIToast(context, text);
+ return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+ mToastStyleEnabled, orientation);
}
private boolean isPluginAvailable() {
@@ -79,5 +91,6 @@
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("ToastFactory:");
pw.println(" mAttachedPlugin=" + mPlugin);
+ pw.println(" mToastStyleEnabled=" + mToastStyleEnabled);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 78173cf..51541bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -49,6 +49,15 @@
})
}
+ fun logOrientationChange(text: String, isPortrait: Boolean) {
+ log(DEBUG, {
+ str1 = text
+ bool1 = isPortrait
+ }, {
+ "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1"
+ })
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 409d136..92ea1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,27 +16,28 @@
package com.android.systemui.toast;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import android.animation.Animator;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
-import android.widget.Toast;
import android.widget.ToastPresenter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Objects;
@@ -58,18 +59,19 @@
private final IAccessibilityManager mIAccessibilityManager;
private final AccessibilityManager mAccessibilityManager;
private final ToastFactory mToastFactory;
- private final DelayableExecutor mMainExecutor;
private final ToastLogger mToastLogger;
private SystemUIToast mToast;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
+ private ToastOutAnimatorListener mToastOutAnimatorListener;
+
+ private int mOrientation = ORIENTATION_PORTRAIT;
@Inject
public ToastUI(
Context context,
CommandQueue commandQueue,
ToastFactory toastFactory,
- @Main DelayableExecutor mainExecutor,
ToastLogger toastLogger) {
this(context, commandQueue,
INotificationManager.Stub.asInterface(
@@ -77,21 +79,19 @@
IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
toastFactory,
- mainExecutor,
toastLogger);
}
@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
@Nullable IAccessibilityManager accessibilityManager,
- ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+ ToastFactory toastFactory, ToastLogger toastLogger
) {
super(context);
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
mIAccessibilityManager = accessibilityManager;
mToastFactory = toastFactory;
- mMainExecutor = mainExecutor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mToastLogger = toastLogger;
}
@@ -105,36 +105,38 @@
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,
IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
- if (mPresenter != null) {
- hideCurrentToast();
- }
- UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
- Context context = mContext.createContextAsUser(userHandle, 0);
- mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+ Runnable showToastRunnable = () -> {
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ Context context = mContext.createContextAsUser(userHandle, 0);
+ mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+ userHandle.getIdentifier(), mOrientation);
- if (mToast.hasCustomAnimation()) {
if (mToast.getInAnimation() != null) {
mToast.getInAnimation().start();
}
- final Animator hideAnimator = mToast.getOutAnimation();
- if (hideAnimator != null) {
- final long durationMillis = duration == Toast.LENGTH_LONG
- ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
- final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
- (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
- mMainExecutor.executeDelayed(() -> hideAnimator.start(),
- updatedDuration - hideAnimator.getTotalDuration());
- }
+
+ mCallback = callback;
+ mPresenter = new ToastPresenter(context, mIAccessibilityManager,
+ mNotificationManager, packageName);
+ // Set as trusted overlay so touches can pass through toasts
+ mPresenter.getLayoutParams().setTrustedOverlay();
+ mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+ mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+ mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
+ };
+
+ if (mToastOutAnimatorListener != null) {
+ // if we're currently animating out a toast, show new toast after prev toast is hidden
+ mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable);
+ } else if (mPresenter != null) {
+ // if there's a toast already showing that we haven't tried hiding yet, hide it and
+ // then show the next toast after its hidden animation is done
+ hideCurrentToast(showToastRunnable);
+ } else {
+ // else, show this next toast immediately
+ showToastRunnable.run();
}
- mCallback = callback;
- mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
- packageName);
- // Set as trusted overlay so touches can pass through toasts
- mPresenter.getLayoutParams().setTrustedOverlay();
- mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
- mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
- mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
- mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
}
@Override
@@ -146,12 +148,88 @@
return;
}
mToastLogger.logOnHideToast(packageName, token.toString());
- hideCurrentToast();
+ hideCurrentToast(null);
}
@MainThread
- private void hideCurrentToast() {
- mPresenter.hide(mCallback);
+ private void hideCurrentToast(Runnable runnable) {
+ if (mToast.getOutAnimation() != null) {
+ Animator animator = mToast.getOutAnimation();
+ mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
+ runnable);
+ animator.addListener(mToastOutAnimatorListener);
+ animator.start();
+ } else {
+ mPresenter.hide(mCallback);
+ if (runnable != null) {
+ runnable.run();
+ }
+ }
+ mToast = null;
mPresenter = null;
+ mCallback = null;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ if (newConfig.orientation != mOrientation) {
+ mOrientation = newConfig.orientation;
+ if (mToast != null) {
+ mToastLogger.logOrientationChange(mToast.mText.toString(),
+ mOrientation == ORIENTATION_PORTRAIT);
+ mToast.onOrientationChange(mOrientation);
+ mPresenter.updateLayoutParams(
+ mToast.getXOffset(),
+ mToast.getYOffset(),
+ mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(),
+ mToast.getGravity());
+ }
+ }
+ }
+
+ /**
+ * Once the out animation for a toast is finished, start showing the next toast.
+ */
+ class ToastOutAnimatorListener implements Animator.AnimatorListener {
+ final ToastPresenter mPrevPresenter;
+ final ITransientNotificationCallback mPrevCallback;
+ @Nullable Runnable mShowNextToastRunnable;
+
+ ToastOutAnimatorListener(
+ @NonNull ToastPresenter presenter,
+ @NonNull ITransientNotificationCallback callback,
+ @Nullable Runnable runnable) {
+ mPrevPresenter = presenter;
+ mPrevCallback = callback;
+ mShowNextToastRunnable = runnable;
+ }
+
+ void setShowNextToastRunnable(Runnable runnable) {
+ mShowNextToastRunnable = runnable;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPrevPresenter.hide(mPrevCallback);
+ if (mShowNextToastRunnable != null) {
+ mShowNextToastRunnable.run();
+ }
+ mToastOutAnimatorListener = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onAnimationEnd(animation);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 09335af..b67574d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -109,7 +109,7 @@
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
(OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(R.string.qs_customize_remove), new OnClickListener() {
+ context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Tell the tuner (in main SysUI process) to clear all its settings.
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 72f1f22..fd3641c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -20,12 +20,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import java.util.HashSet;
import java.util.List;
@@ -163,4 +166,15 @@
}
return apps;
}
+
+ /**
+ * Returns true if the device should use the split notification shade, based on feature flags,
+ * orientation and screen width.
+ */
+ public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
+ Resources resources) {
+ return featureFlags.isTwoColumnNotificationShadeEnabled()
+ && resources.getBoolean(R.bool.config_use_split_notification_shade);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 6e7aed0..afeda967 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -45,6 +45,7 @@
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.NonNull;
@@ -86,10 +87,12 @@
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
@@ -248,38 +251,19 @@
});
mSysuiProxy = new Bubbles.SysuiProxy() {
- private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
- Class clazz) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- return runnable.get();
- }
- final T[] result = (T[]) Array.newInstance(clazz, 1);
- final CountDownLatch latch = new CountDownLatch(1);
- executor.execute(() -> {
- result[0] = runnable.get();
- latch.countDown();
- });
- try {
- latch.await();
- return result[0];
- } catch (InterruptedException e) {
- return null;
- }
- }
-
@Override
- @Nullable
- public BubbleEntry getPendingOrActiveEntry(String key) {
- return executeBlockingForResult(() -> {
+ public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) {
+ sysuiMainExecutor.execute(() -> {
NotificationEntry entry =
mNotificationEntryManager.getPendingOrActiveNotif(key);
- return entry == null ? null : notifToBubbleEntry(entry);
- }, sysuiMainExecutor, BubbleEntry.class);
+ callback.accept(entry == null ? null : notifToBubbleEntry(entry));
+ });
}
@Override
- public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
- return executeBlockingForResult(() -> {
+ public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ Consumer<List<BubbleEntry>> callback) {
+ sysuiMainExecutor.execute(() -> {
List<BubbleEntry> result = new ArrayList<>();
List<NotificationEntry> activeEntries =
mNotificationEntryManager.getActiveNotificationsForCurrentUser();
@@ -291,27 +275,8 @@
result.add(notifToBubbleEntry(entry));
}
}
- return result;
- }, sysuiMainExecutor, List.class);
- }
-
- @Override
- public boolean isNotificationShadeExpand() {
- return executeBlockingForResult(() -> {
- return mNotificationShadeWindowController.getPanelExpanded();
- }, sysuiMainExecutor, Boolean.class);
- }
-
- @Override
- public boolean shouldBubbleUp(String key) {
- return executeBlockingForResult(() -> {
- final NotificationEntry entry =
- mNotificationEntryManager.getPendingOrActiveNotif(key);
- if (entry != null) {
- return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
- }
- return false;
- }, sysuiMainExecutor, Boolean.class);
+ callback.accept(result);
+ });
}
@Override
@@ -587,7 +552,20 @@
}
void onRankingUpdate(RankingMap rankingMap) {
- mBubbles.onRankingUpdated(rankingMap);
+ String[] orderedKeys = rankingMap.getOrderedKeys();
+ HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>();
+ for (int i = 0; i < orderedKeys.length; i++) {
+ String key = orderedKeys[i];
+ NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+ BubbleEntry bubbleEntry = entry != null
+ ? notifToBubbleEntry(entry)
+ : null;
+ boolean shouldBubbleUp = entry != null
+ ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+ : false;
+ pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
+ }
+ mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Handler;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
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.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
import dagger.Provides;
/**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
*/
@Module(includes = {WMShellBaseModule.class})
public abstract class TvPipModule {
@@ -143,7 +145,8 @@
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+ Optional<LegacySplitScreenController> splitScreenOptional,
+ DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.view.IWindowManager;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
import dagger.Provides;
/**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation. Common
+ * dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = {TvPipModule.class})
public class TvWMShellModule {
+
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
+
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
transactionPool);
}
+ //
+ // Split/multiwindow
+ //
+
@WMSingleton
@Provides
- static LegacySplitScreen provideSplitScreen(Context context,
+ static LegacySplitScreenController provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor,
@ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return LegacySplitScreenController.create(context, displayController, systemWindows,
+ return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..8505703 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
import javax.inject.Inject;
/**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components. This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ * -> SystemUIFactory is initialized
+ * -> WMComponent is created
+ * -> WMShellBaseModule dependencies are injected
+ * -> WMShellModule (form-factory specific) dependencies are injected
+ * -> SysUIComponent is created
+ * -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ * -> SysUI services are started
+ * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
*/
@SysUISingleton
public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
@Override
public void start() {
+ // TODO: Consider piping config change and other common calls to a shell component to
+ // delegate internally
mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
@@ -350,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 b42dde6..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;
@@ -32,6 +31,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +45,7 @@
import com.android.wm.shell.TaskViewFactoryController;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
@@ -63,6 +64,7 @@
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -71,7 +73,6 @@
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +86,13 @@
import dagger.Provides;
/**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
@Module
public abstract class WMShellBaseModule {
@@ -174,53 +180,9 @@
}
}
- @WMSingleton
- @Provides
- static ShellInit provideShellInit(DisplayImeController displayImeController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor) {
- return ShellInitImpl.create(displayImeController,
- dragAndDropController,
- shellTaskOrganizer,
- legacySplitScreenOptional,
- splitScreenOptional,
- appPairsOptional,
- fullscreenTaskListener,
- transitions,
- mainExecutor);
- }
-
- /**
- * Note, this is only optional because we currently pass this to the SysUI component scope and
- * for non-primary users, we may inject a null-optional for that dependency.
- */
- @WMSingleton
- @Provides
- static Optional<ShellCommandHandler> provideShellCommandHandler(
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
- legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, appPairsOptional, mainExecutor));
- }
-
- @WMSingleton
- @Provides
- static TransactionPool provideTransactionPool() {
- return new TransactionPool();
- }
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
@WMSingleton
@Provides
@@ -238,8 +200,45 @@
@WMSingleton
@Provides
- static FloatingContentCoordinator provideFloatingContentCoordinator() {
- return new FloatingContentCoordinator();
+ static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+ Context context, SizeCompatUIController sizeCompatUI) {
+ return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+ }
+
+ @WMSingleton
+ @Provides
+ static SizeCompatUIController provideSizeCompatUIController(Context context,
+ DisplayController displayController, DisplayImeController imeController,
+ SyncTransactionQueue syncQueue) {
+ return new SizeCompatUIController(context, displayController, imeController, syncQueue);
+ }
+
+ @WMSingleton
+ @Provides
+ static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SyncTransactionQueue(pool, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static SystemWindows provideSystemWindows(DisplayController displayController,
+ IWindowManager wmService) {
+ return new SystemWindows(displayController, wmService);
+ }
+
+ // We currently dedupe multiple messages, so we use the shell main handler directly
+ @WMSingleton
+ @Provides
+ static TaskStackListenerImpl providerTaskStackListenerImpl(
+ @ShellMainThread Handler mainHandler) {
+ return new TaskStackListenerImpl(mainHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static TransactionPool provideTransactionPool() {
+ return new TransactionPool();
}
@WMSingleton
@@ -249,10 +248,99 @@
return new WindowManagerShellWrapper(mainExecutor);
}
+ //
+ // Bubbles
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+ return bubbleController.map((controller) -> controller.asBubbles());
+ }
+
+ // Note: Handler needed for LauncherApps.register
+ @WMSingleton
+ @Provides
+ static Optional<BubbleController> provideBubbleController(Context context,
+ FloatingContentCoordinator floatingContentCoordinator,
+ IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ ShellTaskOrganizer organizer,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return Optional.of(BubbleController.create(context, null /* synchronizer */,
+ floatingContentCoordinator, statusBarService, windowManager,
+ windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+ mainExecutor, mainHandler));
+ }
+
+ //
+ // Fullscreen
+ //
+
+ @WMSingleton
+ @Provides
+ static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ return new FullscreenTaskListener(syncQueue);
+ }
+
+ //
+ // Hide display cutout
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<HideDisplayCutout> provideHideDisplayCutout(
+ Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+ return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+ }
+
+ @WMSingleton
+ @Provides
+ static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+ DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+ return Optional.ofNullable(
+ HideDisplayCutoutController.create(context, displayController, mainExecutor));
+ }
+
+ //
+ // One handed mode (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+ return oneHandedController.map((controller) -> controller.asOneHanded());
+ }
+
+ // Needs the shell main handler for ContentObserver callbacks
+ @WMSingleton
+ @Provides
+ static Optional<OneHandedController> provideOneHandedController(Context context,
+ DisplayController displayController, TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return Optional.ofNullable(OneHandedController.create(context, displayController,
+ taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+ }
+
+ //
+ // Pip (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static FloatingContentCoordinator provideFloatingContentCoordinator() {
+ return new FloatingContentCoordinator();
+ }
+
@WMSingleton
@Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
- IActivityManager activityManager,
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +356,38 @@
@WMSingleton
@Provides
- static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
- PackageManager packageManager) {
- return new PipUiEventLogger(uiEventLogger, packageManager);
- }
-
- @WMSingleton
- @Provides
static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
@WMSingleton
@Provides
- static SystemWindows provideSystemWindows(DisplayController displayController,
- IWindowManager wmService) {
- return new SystemWindows(displayController, wmService);
+ static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+ PackageManager packageManager) {
+ return new PipUiEventLogger(uiEventLogger, packageManager);
+ }
+
+ //
+ // Shell transitions
+ //
+
+ @WMSingleton
+ @Provides
+ static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+ return Transitions.asRemoteTransitions(transitions);
}
@WMSingleton
@Provides
- static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new SyncTransactionQueue(pool, mainExecutor);
+ static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor) {
+ return new Transitions(organizer, pool, mainExecutor, animExecutor);
}
- @WMSingleton
- @Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
- Context context, SizeCompatUI sizeCompatUI) {
- return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
- }
+ //
+ // Split/multiwindow
+ //
@WMSingleton
@Provides
@@ -307,17 +396,6 @@
return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
}
- // We currently dedupe multiple messages, so we use the shell main handler directly
- @WMSingleton
- @Provides
- static TaskStackListenerImpl providerTaskStackListenerImpl(
- @ShellMainThread Handler mainHandler) {
- return new TaskStackListenerImpl(mainHandler);
- }
-
- @BindsOptionalOf
- abstract LegacySplitScreen optionalLegacySplitScreen();
-
@WMSingleton
@Provides
static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +418,91 @@
}
}
+ // Legacy split (optional feature)
+
+ @WMSingleton
+ @Provides
+ static Optional<LegacySplitScreen> provideLegacySplitScreen(
+ Optional<LegacySplitScreenController> splitScreenController) {
+ return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+ }
+
@BindsOptionalOf
- abstract AppPairs optionalAppPairs();
+ abstract LegacySplitScreenController optionalLegacySplitScreenController();
- // Note: Handler needed for LauncherApps.register
+ // App Pairs (optional feature)
+
@WMSingleton
@Provides
- static Optional<Bubbles> provideBubbles(Context context,
- FloatingContentCoordinator floatingContentCoordinator,
- IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- UiEventLogger uiEventLogger,
- ShellTaskOrganizer organizer,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return Optional.of(BubbleController.create(context, null /* synchronizer */,
- floatingContentCoordinator, statusBarService, windowManager,
- windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
- mainExecutor, mainHandler));
+ static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+ return appPairsController.map((controller) -> controller.asAppPairs());
}
- // Needs the shell main handler for ContentObserver callbacks
+ @BindsOptionalOf
+ abstract AppPairsController optionalAppPairs();
+
+ //
+ // Task view factory
+ //
+
@WMSingleton
@Provides
- static Optional<OneHanded> provideOneHandedController(Context context,
- DisplayController displayController, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return Optional.ofNullable(OneHandedController.create(context, displayController,
- taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+ static Optional<TaskViewFactory> provideTaskViewFactory(
+ TaskViewFactoryController taskViewFactoryController) {
+ return Optional.of(taskViewFactoryController.asTaskViewFactory());
}
@WMSingleton
@Provides
- static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
- DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(
- HideDisplayCutoutController.create(context, displayController, mainExecutor));
- }
-
- @WMSingleton
- @Provides
- static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+ static TaskViewFactoryController provideTaskViewFactoryController(
+ ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
- .getTaskViewFactory());
+ return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
}
+ //
+ // Misc
+ //
+
@WMSingleton
@Provides
- static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
- return new FullscreenTaskListener(syncQueue);
- }
-
- @WMSingleton
- @Provides
- static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
- return Transitions.asRemoteTransitions(transitions);
- }
-
- @WMSingleton
- @Provides
- static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, mainExecutor, animExecutor);
- }
-
- @WMSingleton
- @Provides
- static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
- DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
- return SizeCompatUIController.create(context, displayController, imeController,
+ static ShellInit provideShellInit(DisplayImeController displayImeController,
+ DragAndDropController dragAndDropController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<AppPairsController> appPairsOptional,
+ FullscreenTaskListener fullscreenTaskListener,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return ShellInitImpl.create(displayImeController,
+ dragAndDropController,
+ shellTaskOrganizer,
+ legacySplitScreenOptional,
+ splitScreenOptional,
+ appPairsOptional,
+ fullscreenTaskListener,
+ transitions,
mainExecutor);
}
+
+ /**
+ * Note, this is only optional because we currently pass this to the SysUI component scope and
+ * for non-primary users, we may inject a null-optional for that dependency.
+ */
+ @WMSingleton
+ @Provides
+ static Optional<ShellCommandHandler> provideShellCommandHandler(
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<Pip> pipOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+ legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+ hideDisplayCutout, appPairsOptional, mainExecutor));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
import android.os.Handler;
import android.view.IWindowManager;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
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.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
import dagger.Provides;
/**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation. Common
+ * dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = WMShellBaseModule.class)
public class WMShellModule {
+
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
+
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
transactionPool);
}
+ //
+ // Split/multiwindow
+ //
+
@WMSingleton
@Provides
- static LegacySplitScreen provideLegacySplitScreen(Context context,
+ static LegacySplitScreenController provideLegacySplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor,
@ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return LegacySplitScreenController.create(context, displayController, systemWindows,
+ return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
@WMSingleton
@Provides
- static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+ static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
- return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+ return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
mainExecutor);
}
+ //
+ // Pip
+ //
+
@WMSingleton
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
PipTransitionController pipTransitionController,
- Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+ Optional<LegacySplitScreenController> splitScreenOptional,
+ DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
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/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
new file mode 100644
index 0000000..9278570
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
+import java.util.Optional
+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.`when`
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlActionCoordinatorImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var lazyUiController: Lazy<ControlsUiController>
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var bgExecutor: DelayableExecutor
+ @Mock
+ private lateinit var uiExecutor: DelayableExecutor
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var globalActionsComponent: GlobalActionsComponent
+ @Mock
+ private lateinit var taskViewFactory: Optional<TaskViewFactory>
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private lateinit var cvh: ControlViewHolder
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+
+ private val ID = "id"
+ }
+
+ private lateinit var coordinator: ControlActionCoordinatorImpl
+ private lateinit var action: ControlActionCoordinatorImpl.Action
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ coordinator = spy(ControlActionCoordinatorImpl(
+ mContext,
+ bgExecutor,
+ uiExecutor,
+ activityStarter,
+ keyguardStateController,
+ globalActionsComponent,
+ taskViewFactory,
+ getFakeBroadcastDispatcher(),
+ lazyUiController
+ ))
+
+ `when`(cvh.cws.ci.controlId).thenReturn(ID)
+ action = spy(coordinator.Action(ID, {}, false))
+ doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
+ }
+
+ @Test
+ fun testToggleRunsWhenUnlocked() {
+ `when`(keyguardStateController.isShowing()).thenReturn(false)
+
+ coordinator.toggle(cvh, "", true)
+ verify(coordinator).bouncerOrRun(action)
+ verify(action).invoke()
+ }
+
+ @Test
+ fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
+ `when`(keyguardStateController.isShowing()).thenReturn(true)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+
+ coordinator.toggle(cvh, "", true)
+ verify(coordinator).bouncerOrRun(action)
+ verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ verify(action, never()).invoke()
+
+ // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
+ reset(action)
+ coordinator.runPendingAction(ID)
+ verify(action, never()).invoke()
+
+ `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+ reset(action)
+ coordinator.runPendingAction(ID)
+ verify(action).invoke()
+ }
+}
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/emergency/EmergencyActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
index a52a598..0457100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
@@ -19,7 +19,7 @@
import android.app.Activity;
import android.os.Bundle;
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
/**
* Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action.
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/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 2db224f..e88c728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -315,10 +315,9 @@
}
mediaDataManager.onNotificationAdded(KEY, notif)
- // THEN it loads and uses the default background color
+ // THEN it still loads
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
- assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR)
}
}
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/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0dc268a..fb817ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +30,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -37,6 +41,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
@@ -44,6 +49,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.animation.DisappearParameters;
import org.junit.Before;
@@ -86,17 +92,25 @@
QSTileView mQSTileView;
@Mock
PagedTileLayout mPagedTileLayout;
+ @Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
+ Resources mResources;
+ @Mock
+ Configuration mConfiguration;
private QSPanelControllerBase<QSPanel> mController;
+
+
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager) {
+ DumpManager dumpManager, FeatureFlags featureFlags) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager);
+ qsLogger, dumpManager, featureFlags);
}
@Override
@@ -121,10 +135,12 @@
when(mQSTileRevealControllerFactory.create(any(), any()))
.thenReturn(mQSTileRevealController);
when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+ when(mQSPanel.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
mController.init();
reset(mQSTileRevealController);
@@ -136,7 +152,7 @@
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
mQSTileHost, mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -218,4 +234,18 @@
verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
verify(mPagedTileLayout).setListening(false, mUiEventLogger);
}
+
+
+ @Test
+ public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ when(mMediaHost.getVisible()).thenReturn(true);
+
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 4381158..0dfebab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.DisappearParameters;
@@ -93,6 +94,8 @@
QSTileView mQSTileView;
@Mock
PagedTileLayout mPagedTileLayout;
+ @Mock
+ FeatureFlags mFeatureFlags;
private QSPanelController mController;
@@ -118,7 +121,9 @@
mQSTileHost, mQSCustomizerController, true, mMediaHost,
mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
- /* labelsFlag */ false);
+ /* labelsFlag */ false,
+ mFeatureFlags
+ );
mController.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 107160f..5870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -63,6 +64,8 @@
private lateinit var tileLayout: TileLayout
@Mock
private lateinit var tileView: QSTileView
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
private lateinit var controller: QuickQSPanelController
@@ -84,7 +87,8 @@
uiEventLogger,
qsLogger,
dumpManager,
- false
+ false,
+ featureFlags
)
controller.init()
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 b452d3a..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));
@@ -98,7 +98,7 @@
mQSCarrierGroupController = new QSCarrierGroupController.Builder(
mActivityStarter, handler, TestableLooper.get(this).getLooper(),
- mNetworkController, mCarrierTextControllerBuilder)
+ mNetworkController, mCarrierTextControllerBuilder, mContext)
.setQSCarrierGroup(mQSCarrierGroup)
.build();
@@ -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
new file mode 100644
index 0000000..d3dbe2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -0,0 +1,295 @@
+/*
+ * 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.qs.tiles
+
+import android.os.Handler
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.LifecycleOwner
+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
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.plugins.ActivityStarter
+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
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DeviceControlsTileTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var qsHost: QSHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ private lateinit var controlsComponent: ControlsComponent
+ @Mock
+ private lateinit var controlsUiController: ControlsUiController
+ @Mock
+ private lateinit var controlsListingController: ControlsListingController
+ @Mock
+ private lateinit var controlsController: ControlsController
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var controlsDialog: ControlsDialog
+ private lateinit var globalSettings: GlobalSettings
+ @Mock
+ private lateinit var serviceInfo: ControlsServiceInfo
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+ @Captor
+ private lateinit var listingCallbackCaptor:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+ 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)
+ testableLooper = TestableLooper.get(this)
+
+ `when`(qsHost.context).thenReturn(mContext)
+ `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+ controlsComponent = ControlsComponent(
+ true,
+ mContext,
+ { controlsController },
+ { controlsUiController },
+ { controlsListingController },
+ lockPatternUtils,
+ keyguardStateController,
+ userTracker,
+ secureSettings
+ )
+
+ globalSettings = FakeSettings()
+
+ 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()
+ }
+
+ @Test
+ fun testNotAvailableControls() {
+ controlsComponent = ControlsComponent(
+ false,
+ mContext,
+ { controlsController },
+ { controlsUiController },
+ { controlsListingController },
+ lockPatternUtils,
+ keyguardStateController,
+ userTracker,
+ secureSettings
+ )
+ tile = createTile()
+
+ assertThat(tile.isAvailable).isFalse()
+ }
+
+ @Test
+ fun testNotAvailableFlag() {
+ globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
+ tile = createTile()
+
+ assertThat(tile.isAvailable).isFalse()
+ }
+
+ @Test
+ fun testObservingCallback() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ any(ControlsListingController.ControlsListingCallback::class.java)
+ )
+ }
+
+ @Test
+ fun testLongClickIntent() {
+ assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+ }
+
+ @Test
+ fun testUnavailableByDefault() {
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testStateUnavailableIfNoListings() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(emptyList())
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testStateAvailableIfListings() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ }
+
+ @Test
+ fun testMoveBetweenStates() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ listingCallbackCaptor.value.onServicesUpdated(emptyList())
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testNoDialogWhenUnavailable() {
+ tile.click()
+ testableLooper.processAllMessages()
+
+ verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+ }
+
+ @Test
+ fun testDialogShowWhenAvailable() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ tile.click()
+ testableLooper.processAllMessages()
+
+ verify(controlsDialog).show(controlsUiController)
+ }
+
+ @Test
+ fun testDialogDismissedOnDestroy() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ tile.click()
+ testableLooper.processAllMessages()
+
+ tile.destroy()
+ testableLooper.processAllMessages()
+ verify(controlsDialog).dismiss()
+ }
+
+ private fun createTile(): DeviceControlsTile {
+ return DeviceControlsTile(
+ qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ controlsComponent,
+ featureFlags,
+ { controlsDialog },
+ globalSettings
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index c1c6371..580f800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -57,6 +57,8 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ScrollCaptureClientTest extends SysuiTestCase {
+ private static final float MAX_PAGES = 3f;
+
private Context mContext;
private IWindowManager mWm;
@@ -96,7 +98,7 @@
Connection conn = mConnectionConsumer.getValue();
- conn.start(mSessionConsumer);
+ conn.start(mSessionConsumer, MAX_PAGES);
verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
Session session = mSessionConsumer.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
index bd37259..4c84df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
@@ -34,7 +34,7 @@
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView text = new TextView(this);
text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40);
- text.setText(com.android.systemui.R.string.test_content);
+ text.setText(com.android.systemui.tests.R.string.test_content);
linearLayout.addView(text);
scrollView.addView(linearLayout);
setContentView(scrollView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index d131dce..e16d4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -36,16 +36,20 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.os.Handler;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.util.Pair;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.DeviceConfigProxyFake;
import junit.framework.Assert;
@@ -55,38 +59,44 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class AssistantFeedbackControllerTest extends SysuiTestCase {
- private static final int ON = 1;
- private static final int OFF = 0;
private static final String TEST_PACKAGE_NAME = "test_package";
private static final int TEST_UID = 1;
private AssistantFeedbackController mAssistantFeedbackController;
+ private DeviceConfigProxyFake mProxyFake;
+ private TestableLooper mTestableLooper;
+
private StatusBarNotification mSbn;
@Before
public void setUp() {
- mAssistantFeedbackController = new AssistantFeedbackController(mContext);
- switchSetting(ON);
+ mProxyFake = new DeviceConfigProxyFake();
+ mTestableLooper = TestableLooper.get(this);
+ mAssistantFeedbackController = new AssistantFeedbackController(
+ new Handler(mTestableLooper.getLooper()),
+ mContext, mProxyFake);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
0, null, TEST_UID, 0, new Notification(),
UserHandle.CURRENT, null, 0);
}
@Test
- public void testUserControls_settingDisabled() {
- switchSetting(OFF);
+ public void testFlagDisabled() {
+ switchFlag("false");
assertFalse(mAssistantFeedbackController.isFeedbackEnabled());
}
@Test
- public void testUserControls_settingEnabled() {
+ public void testFlagEnabled() {
+ switchFlag("true");
assertTrue(mAssistantFeedbackController.isFeedbackEnabled());
}
@Test
- public void testFeedback_settingDisabled() {
- switchSetting(OFF);
+ public void testFeedback_flagDisabled() {
+ switchFlag("false");
assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus(
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
assertFalse(mAssistantFeedbackController.showFeedbackIndicator(
@@ -95,6 +105,7 @@
@Test
public void testFeedback_changedImportance() {
+ switchFlag("true");
NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED);
assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
assertTrue(mAssistantFeedbackController.showFeedbackIndicator(entry));
@@ -110,6 +121,7 @@
@Test
public void testFeedback_changedRanking() {
+ switchFlag("true");
NotificationEntry entry =
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED);
assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
@@ -121,8 +133,8 @@
}
@Test
- public void testGetFeedbackResources_settingDisabled() {
- switchSetting(OFF);
+ public void testGetFeedbackResources_flagDisabled() {
+ switchFlag("false");
Assert.assertEquals(new Pair(0, 0), mAssistantFeedbackController.getFeedbackResources(
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
}
@@ -138,9 +150,10 @@
.build();
}
- private void switchSetting(int setting) {
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting);
- mAssistantFeedbackController.update(null);
+ private void switchFlag(String enabled) {
+ mProxyFake.setProperty(
+ DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK,
+ enabled, false);
+ mTestableLooper.processAllMessages();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6b0a23f..2e2945e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,7 +50,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -61,6 +60,7 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
+import com.android.systemui.tests.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 6fcc7fa..64a7bee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -43,7 +43,6 @@
import android.view.LayoutInflater;
import android.widget.RemoteViews;
-import com.android.systemui.R;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.media.MediaFeatureFlag;
@@ -68,6 +67,7 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.tests.R;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
import com.android.wm.shell.bubbles.Bubbles;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index a147c8d..45f7c5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.tests.R;
import org.junit.Assert;
import org.junit.Before;
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 b9fd75e..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;
@@ -114,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 ee1d758..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
@@ -393,10 +393,11 @@
private void positionClock() {
mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
+ mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+ 0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */,
mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
- mQsExpansion, mCutoutTopInset);
+ 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 c07ba72..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
@@ -37,6 +37,7 @@
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.os.PowerManager;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
@@ -47,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;
@@ -57,7 +60,10 @@
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;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -193,12 +199,24 @@
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
+ 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;
@@ -213,11 +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() {
@@ -250,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(
@@ -283,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,
@@ -299,11 +324,15 @@
mBiometricUnlockController, mStatusBarKeyguardViewManager,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
+ mKeyguardQsUserSwitchComponentFactory,
+ mKeyguardUserSwitcherComponentFactory,
+ mKeyguardStatusBarViewComponentFactory,
+ mQSDetailDisplayer,
mGroupManager,
mNotificationAreaController,
mAuthController,
- new QSDetailDisplayer(),
mScrimController,
+ mUserManager,
mMediaDataManager,
mAmbientState,
mFeatureFlags,
@@ -425,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();
@@ -442,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/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index c212cf3..67c1a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -26,12 +26,12 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.settingslib.R;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tests.R;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index fc1a791..e479882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -60,9 +60,9 @@
@Mock
private lateinit var layoutInflater: LayoutInflater
@Mock
- private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+ private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
- private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+ private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
private lateinit var picture: Bitmap
@Before
@@ -72,8 +72,11 @@
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
`when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(inflatedUserDetailItemView)
- adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
- keyguardUserSwitcher)
+ adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
+ mContext,
+ mContext.resources,
+ LayoutInflater.from(mContext),
+ userSwitcherController, keyguardUserSwitcherController)
picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
@@ -118,11 +121,11 @@
}
@Test
- fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+ fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
val v: UserDetailItemView? = createViewFromSameType(
isCurrentUser = true, isGuestUser = false)
assertNotNull(v)
- verify(v)!!.setOnClickListener(null)
+ verify(v)!!.setOnClickListener(adapter)
}
@Test
@@ -150,11 +153,11 @@
}
@Test
- fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+ fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
val v: UserDetailItemView? = createViewFromDifferentType(
isCurrentUser = true, isGuestUser = false)
assertNotNull(v)
- verify(v)!!.setOnClickListener(null)
+ verify(v)!!.setOnClickListener(adapter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index f8b6383..89cc2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -430,6 +430,10 @@
updateSignalStrength();
}
+ public void setImsType(int imsType) {
+ mMobileSignalController.setImsType(imsType);
+ }
+
public void setIsGsm(boolean gsm) {
when(mSignalStrength.isGsm()).thenReturn(gsm);
updateSignalStrength();
@@ -632,6 +636,14 @@
}
}
+ protected void verifyLastCallStrength(int icon) {
+ ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setCallIndicator(
+ iconArg.capture(),
+ anyInt());
+ assertEquals("Call strength, in status bar", icon, (int) iconArg.getValue().icon);
+ }
+
protected void assertNetworkNameEquals(String expected) {
assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 10166cb..fc1a08ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -15,6 +15,7 @@
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.telephony.CellSignalStrength;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -243,6 +244,28 @@
}
}
+ @Test
+ public void testCallStrengh() {
+ String testSsid = "Test SSID";
+ setWifiEnabled(true);
+ setWifiState(true, testSsid);
+ // Set the ImsType to be IMS_TYPE_WLAN
+ setImsType(2);
+ setWifiLevel(1);
+ for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+ setWifiLevel(testLevel);
+ verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]);
+ }
+ // Set the ImsType to be IMS_TYPE_WWAN
+ setImsType(1);
+ for (int testStrength = 0;
+ testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
+ setupDefaultSignal();
+ setLevel(testStrength);
+ verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
+ }
+ }
+
protected void setWifiActivity(int activity) {
// TODO: Not this, because this variable probably isn't sticking around.
mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index edaff5f..45828c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -32,13 +32,18 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -70,11 +75,12 @@
private static final String TEST_DISABLED_PREFIX = "com.example.";
private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
- private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap();
+ private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap();
static {
for (String category : THEME_CATEGORIES) {
- ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category);
+ ALL_CATEGORIES_MAP.put(category,
+ new OverlayIdentifier(TEST_DISABLED_PREFIX + category));
}
}
@@ -87,6 +93,8 @@
OverlayManager mOverlayManager;
@Mock
DumpManager mDumpManager;
+ @Mock
+ OverlayManagerTransaction.Builder mTransactionBuilder;
private ThemeOverlayApplier mManager;
@@ -94,7 +102,12 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
- LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
+ LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+ @Override
+ protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+ return mTransactionBuilder;
+ }
+ };
when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
.thenReturn(Lists.newArrayList(
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
@@ -147,24 +160,26 @@
@Test
public void allCategoriesSpecified_allEnabledExclusively() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
+ verify(mOverlayManager).commit(any());
- for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
}
@Test
public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
- for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
+ for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true),
+ eq(UserHandle.SYSTEM.getIdentifier()));
} else {
- verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
}
}
}
@@ -174,17 +189,34 @@
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
UserHandle newUserHandle = UserHandle.of(10);
userHandles.add(newUserHandle);
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles);
- for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+ for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(newUserHandle.getIdentifier()));
+ }
+ }
+
+ @Test
+ public void applyCurrentUserOverlays_createsPendingOverlays() {
+ Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
+ UserHandle newUserHandle = UserHandle.of(10);
+ userHandles.add(newUserHandle);
+ FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] {
+ mock(FabricatedOverlay.class)
+ };
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles);
+
+ for (FabricatedOverlay overlay : pendingCreation) {
+ verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
}
}
@Test
public void allCategoriesSpecified_overlayManagerNotQueried() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
verify(mOverlayManager, never())
.getOverlayInfosForTarget(anyString(), any(UserHandle.class));
@@ -192,48 +224,56 @@
@Test
public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
- for (String overlayPackage : categoryToPackage.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS,
- false, TEST_USER);
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
- false, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
+ eq(false), eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)),
+ eq(false), eq(TEST_USER.getIdentifier()));
}
@Test
public void zeroCategoriesSpecified_allDisabled() {
- mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES);
for (String category : THEME_CATEGORIES) {
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false),
+ eq(TEST_USER.getIdentifier()));
}
}
@Test
public void nonThemeCategorySpecified_ignored() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
- categoryToPackage.put("blah.category", "com.example.blah.category");
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
- verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER);
- verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category",
- TEST_USER);
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
+ eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(new OverlayIdentifier("com.example.blah.category")), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
@Test
public void overlayManagerOnlyQueriedForUnspecifiedPackages() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM);
verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE,
@@ -247,7 +287,8 @@
private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName,
String category, boolean enabled) {
- return new OverlayInfo(packageName, targetPackageName, null, category, "",
- enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false);
+ return new OverlayInfo(packageName, null, targetPackageName, null, category, "",
+ enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false,
+ false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d33fac0..f7f8d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.theme;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
@@ -27,12 +25,15 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.graphics.Color;
import android.os.Handler;
import android.os.UserHandle;
@@ -40,11 +41,13 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
@@ -56,7 +59,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -85,6 +87,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
@Captor
@@ -93,10 +97,20 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mFeatureFlags.isMonetEnabled()).thenReturn(true);
mThemeOverlayController = new ThemeOverlayController(null /* context */,
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
- mDumpManager);
+ mDumpManager, mFeatureFlags) {
+ @Nullable
+ @Override
+ protected FabricatedOverlay getOverlay(int color, int type) {
+ FabricatedOverlay overlay = mock(FabricatedOverlay.class);
+ when(overlay.getIdentifier())
+ .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
+ return overlay;
+ }
+ };
mThemeOverlayController.start();
if (USE_LOCK_SCREEN_WALLPAPER) {
@@ -106,10 +120,6 @@
verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
eq(UserHandle.USER_ALL));
verify(mDumpManager).registerDumpable(any(), any());
-
- List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC);
- when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
- when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
}
@Test
@@ -128,17 +138,17 @@
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
- verify(mThemeOverlayApplier).getAvailableSystemColors();
- verify(mThemeOverlayApplier).getAvailableAccentColors();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000");
+ .isEqualTo(new OverlayIdentifier("ffff0000"));
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
- .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF");
+ .isEqualTo(new OverlayIdentifier("ff0000ff"));
// Should not ask again if changed to same value
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
@@ -146,49 +156,6 @@
}
@Test
- public void onWallpaperColorsChanged_whiteTheme() {
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE),
- Color.valueOf(Color.BLUE), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
- }
-
- @Test
- public void onWallpaperColorsChanged_blackTheme() {
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK),
- Color.valueOf(Color.BLUE), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
- }
-
- @Test
- public void onWallpaperColorsChanged_addsLeadingZerosToColors() {
- // Should ask for a new theme when wallpaper colors change
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC),
- Color.valueOf(0x000CCC), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC");
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
- .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC");
- }
-
- @Test
public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -201,14 +168,37 @@
.thenReturn(jsonString);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
- verify(mThemeOverlayApplier).getAvailableSystemColors();
- verify(mThemeOverlayApplier).getAvailableAccentColors();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo("override.package.name");
+ .isEqualTo(new OverlayIdentifier("override.package.name"));
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() {
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
+
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
+
+ // Assert that we received the colors that we were expecting
+ assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+ .isEqualTo(new OverlayIdentifier("ff00ff00"));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index c743fd0..365c62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -57,8 +57,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.FeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -88,7 +87,6 @@
private static final String TEXT = "Hello World";
private static final int MESSAGE_RES_ID = R.id.message;
- private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
private Context mContextSpy;
private ToastUI mToastUI;
@Mock private LayoutInflater mLayoutInflater;
@@ -99,6 +97,7 @@
@Mock private PluginManager mPluginManager;
@Mock private DumpManager mDumpManager;
@Mock private ToastLogger mToastLogger;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private ITransientNotificationCallback mCallback;
@Captor private ArgumentCaptor<View> mViewCaptor;
@@ -107,12 +106,9 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- // This is because inflate will result in WindowManager (WM) calls, which will fail since we
- // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM.
- View view = ToastPresenter.getTextToastView(mContext, TEXT);
- when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view);
- mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater);
+ when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
+ ToastPresenter.getTextToastView(mContext, TEXT));
+ when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
mContextSpy = spy(mContext);
@@ -120,8 +116,8 @@
doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
- mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
- mFakeDelayableExecutor, mToastLogger);
+ mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
+ mDumpManager, mFeatureFlags), mToastLogger);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c0af15b..203ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -19,8 +19,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.List;
@@ -66,7 +66,7 @@
}
@Override
- public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+ public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
mBubblesManager = new BubblesManager(
mContext,
- mBubbleController.getImpl(),
+ mBubbleController.asBubbles(),
mNotificationShadeWindowController,
mStatusBarStateController,
mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index f4d96a12..ab329c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
@@ -20,7 +20,7 @@
import android.content.Intent;
import android.os.Bundle;
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
/**
* Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
mBubblesManager = new BubblesManager(
mContext,
- mBubbleController.getImpl(),
+ mBubbleController.asBubbles(),
mNotificationShadeWindowController,
mStatusBarStateController,
mShadeController,
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/core/Android.bp b/services/core/Android.bp
index 25890b0..9f36910 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,8 +81,6 @@
":dumpstate_aidl",
":framework_native_aidl",
":gsiservice_aidl",
- ":idmap2_aidl",
- ":idmap2_core_aidl",
":inputconstants_aidl",
":installd_aidl",
":storaged_aidl",
@@ -131,8 +129,8 @@
"android.hardware.light-V1-java",
"android.hardware.tv.cec-V1.0-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",
@@ -230,8 +228,5 @@
"java/com/android/server/connectivity/QosCallbackAgentConnection.java",
"java/com/android/server/connectivity/QosCallbackTracker.java",
"java/com/android/server/connectivity/TcpKeepaliveController.java",
- "java/com/android/server/connectivity/Vpn.java",
- "java/com/android/server/connectivity/VpnIkev2Utils.java",
- "java/com/android/server/net/LockdownVpnTracker.java",
],
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 277152d..558fbc2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
package com.android.server;
-import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
@@ -45,8 +44,9 @@
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;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -95,6 +95,7 @@
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.InetAddresses;
@@ -138,7 +139,6 @@
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
-import android.net.VpnService;
import android.net.VpnTransportInfo;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
@@ -170,8 +170,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Credentials;
-import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -187,9 +185,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
@@ -214,9 +209,7 @@
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
-import com.android.server.net.LockdownVpnTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.utils.PriorityDump;
@@ -309,18 +302,7 @@
private final PerUidCounter mNetworkRequestCounter;
- private KeyStore mKeyStore;
-
- @VisibleForTesting
- @GuardedBy("mVpns")
- protected final SparseArray<Vpn> mVpns = new SparseArray<>();
-
- // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
- // a direct call to LockdownVpnTracker.isEnabled().
- @GuardedBy("mVpns")
- private boolean mLockdownEnabled;
- @GuardedBy("mVpns")
- private LockdownVpnTracker mLockdownTracker;
+ private volatile boolean mLockdownEnabled;
/**
* Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
@@ -571,6 +553,12 @@
private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
/**
+ * used internally when setting the default networks for OemNetworkPreferences.
+ * obj = OemNetworkPreferences
+ */
+ private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -755,6 +743,27 @@
}
}
+ // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying
+ // network type, to preserve previous behaviour.
+ private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) {
+ if (vpnNai != mService.getLegacyLockdownNai()) return;
+
+ if (vpnNai.declaredUnderlyingNetworks == null
+ || vpnNai.declaredUnderlyingNetworks.length != 1) {
+ Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: "
+ + Arrays.toString(vpnNai.declaredUnderlyingNetworks));
+ return;
+ }
+ final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
+ vpnNai.declaredUnderlyingNetworks[0]);
+ if (underlyingNai == null) return;
+
+ final int type = underlyingNai.networkInfo.getType();
+ final DetailedState state = DetailedState.CONNECTED;
+ maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */);
+ mService.sendLegacyNetworkBroadcast(underlyingNai, state, type);
+ }
+
/** Adds the given network to the specified legacy type list. */
public void add(int type, NetworkAgentInfo nai) {
if (!isTypeSupported(type)) {
@@ -772,9 +781,17 @@
// Send a broadcast if this is the first network of its type or if it's the default.
final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
+
+ // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts
+ // to preserve previous behaviour.
+ final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED);
if ((list.size() == 1) || isDefaultNetwork) {
- maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
- mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+ maybeLogBroadcast(nai, state, type, isDefaultNetwork);
+ mService.sendLegacyNetworkBroadcast(nai, state, type);
+ }
+
+ if (type == TYPE_VPN && state == DetailedState.CONNECTED) {
+ maybeSendLegacyLockdownBroadcast(nai);
}
}
@@ -969,13 +986,6 @@
}
/**
- * Get a reference to the system keystore.
- */
- public KeyStore getKeyStore() {
- return KeyStore.getInstance();
- }
-
- /**
* @see ProxyTracker
*/
public ProxyTracker makeProxyTracker(@NonNull Context context,
@@ -1039,10 +1049,10 @@
mMetricsLog = logger;
mNetworkRanker = new NetworkRanker();
- final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
- -1, NetworkRequest.Type.REQUEST);
- mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder(),
- null /* attributionTag */);
+ final NetworkRequest defaultInternetRequest = createDefaultRequest();
+ mDefaultRequest = new NetworkRequestInfo(
+ defaultInternetRequest, null, new Binder(),
+ null /* attributionTags */);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1084,7 +1094,6 @@
mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
mNetd = netd;
- mKeyStore = mDeps.getKeyStore();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = new LocationPermissionChecker(mContext);
@@ -1173,43 +1182,15 @@
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
- // Set up the listener for user state for creating user VPNs.
+ // Listen for user add/removes to inform PermissionMonitor.
// Should run on mHandler to avoid any races.
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_STARTED);
- intentFilter.addAction(Intent.ACTION_USER_STOPPED);
intentFilter.addAction(Intent.ACTION_USER_ADDED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
- mUserAllContext.registerReceiver(
- mIntentReceiver,
- intentFilter,
- null /* broadcastPermission */,
- mHandler);
- mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
- mUserPresentReceiver,
- new IntentFilter(Intent.ACTION_USER_PRESENT),
- null /* broadcastPermission */,
- null /* scheduler */);
-
- // Listen to package add and removal events for all users.
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mUserAllContext.registerReceiver(
- mIntentReceiver,
- intentFilter,
- null /* broadcastPermission */,
- mHandler);
-
- // Listen to lockdown VPN reset.
- intentFilter = new IntentFilter();
- intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
- mUserAllContext.registerReceiver(
- mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+ mUserAllContext.registerReceiver(mIntentReceiver, intentFilter,
+ null /* broadcastPermission */, mHandler);
mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS);
@@ -1250,21 +1231,29 @@
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;
}
+ private NetworkRequest createDefaultRequest() {
+ return createDefaultInternetRequestForTransport(
+ TYPE_NONE, NetworkRequest.Type.REQUEST);
+ }
+
private NetworkRequest createDefaultInternetRequestForTransport(
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 > -1) {
+ if (transportType > TYPE_NONE) {
netCap.addTransportType(transportType);
}
+ return createNetworkRequest(type, netCap);
+ }
+
+ private NetworkRequest createNetworkRequest(
+ NetworkRequest.Type type, NetworkCapabilities netCap) {
return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
}
@@ -1314,7 +1303,8 @@
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
- null, networkRequest, new Binder(), null /* attributionTag */));
+ networkRequest, null, new Binder(),
+ null /* attributionTags */));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
@@ -1387,9 +1377,7 @@
}
private Network[] getVpnUnderlyingNetworks(int uid) {
- synchronized (mVpns) {
- if (mLockdownEnabled) return null;
- }
+ if (mLockdownEnabled) return null;
final NetworkAgentInfo nai = getVpnForUid(uid);
if (nai != null) return nai.declaredUnderlyingNetworks;
return null;
@@ -1474,11 +1462,9 @@
if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
}
- synchronized (mVpns) {
- if (mLockdownTracker != null) {
- mLockdownTracker.augmentNetworkInfo(networkInfo);
- }
- }
+ networkInfo.setDetailedState(
+ getLegacyLockdownState(networkInfo.getDetailedState()),
+ "" /* reason */, null /* extraInfo */);
}
/**
@@ -1537,14 +1523,6 @@
return nai.network;
}
- // Public because it's used by mLockdownTracker.
- public NetworkInfo getActiveNetworkInfoUnfiltered() {
- enforceAccessPermission();
- final int uid = mDeps.getCallingUid();
- NetworkState state = getUnfilteredActiveNetworkState(uid);
- return state.networkInfo;
- }
-
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
NetworkStack.checkNetworkStackPermission(mContext);
@@ -2166,22 +2144,6 @@
isBackgroundRestricted);
}
- /**
- * Require that the caller is either in the same user or has appropriate permission to interact
- * across users.
- *
- * @param userId Target user for whatever operation the current IPC is supposed to perform.
- */
- private void enforceCrossUserPermission(int userId) {
- if (userId == UserHandle.getCallingUserId()) {
- // Not a cross-user call.
- return;
- }
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "ConnectivityService");
- }
-
private boolean checkAnyPermissionOf(String... permissions) {
for (String permission : permissions) {
if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
@@ -2262,12 +2224,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
}
- private void enforceControlAlwaysOnVpnPermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
- "ConnectivityService");
- }
-
private void enforceNetworkStackOrSettingsPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
@@ -2292,6 +2248,12 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
+ private void enforceOemNetworkPreferencesPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE,
+ "ConnectivityService");
+ }
+
private boolean checkNetworkStackPermission() {
return checkAnyPermissionOf(
android.Manifest.permission.NETWORK_STACK,
@@ -2340,13 +2302,6 @@
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
- synchronized (mVpns) {
- if (mLockdownTracker != null) {
- info = new NetworkInfo(info);
- mLockdownTracker.augmentNetworkInfo(info);
- }
- }
-
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -2440,10 +2395,6 @@
}
}
- // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
- // for user to unlock device too.
- updateLockdownVpn();
-
// Create network requests for always-on networks.
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
}
@@ -2634,6 +2585,12 @@
}
pw.println();
+ pw.print("Current per-app default networks: ");
+ pw.increaseIndent();
+ dumpPerAppNetworkPreferences(pw);
+ pw.decreaseIndent();
+ pw.println();
+
pw.println("Current Networks:");
pw.increaseIndent();
dumpNetworks(pw);
@@ -2754,6 +2711,40 @@
}
}
+ private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) {
+ pw.println("Per-App Network Preference:");
+ pw.increaseIndent();
+ if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) {
+ pw.println("none");
+ } else {
+ pw.println(mOemNetworkPreferences.toString());
+ }
+ pw.decreaseIndent();
+
+ for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) {
+ if (mDefaultRequest == defaultRequest) {
+ continue;
+ }
+
+ final boolean isActive = null != defaultRequest.getSatisfier();
+ pw.println("Is per-app network active:");
+ pw.increaseIndent();
+ pw.println(isActive);
+ if (isActive) {
+ pw.println("Active network: " + defaultRequest.getSatisfier().network.netId);
+ }
+ pw.println("Tracked UIDs:");
+ pw.increaseIndent();
+ if (0 == defaultRequest.mRequests.size()) {
+ pw.println("none, this should never occur.");
+ } else {
+ pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids());
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ }
+
private void dumpNetworkRequests(IndentingPrintWriter pw) {
for (NetworkRequestInfo nri : requestsSortedById()) {
pw.println(nri.toString());
@@ -2887,7 +2878,15 @@
Log.wtf(TAG, "Non-virtual networks cannot have underlying networks");
break;
}
+
final List<Network> underlying = (List<Network>) arg.second;
+
+ if (isLegacyLockdownNai(nai)
+ && (underlying == null || underlying.size() != 1)) {
+ Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString()
+ + " must have exactly one underlying network: " + underlying);
+ }
+
final Network[] oldUnderlying = nai.declaredUnderlyingNetworks;
nai.declaredUnderlyingNetworks = (underlying != null)
? underlying.toArray(new Network[0]) : null;
@@ -3496,7 +3495,6 @@
// incorrect) behavior.
mNetworkActivityTracker.updateDataActivityTracking(
null /* newNetwork */, nai);
- notifyLockdownVpn(nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -3586,29 +3584,38 @@
}
private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+ handleRegisterNetworkRequest(Collections.singletonList(nri));
+ }
+
+ private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
- mNetworkRequestInfoLogs.log("REGISTER " + nri);
- for (final NetworkRequest req : nri.mRequests) {
- mNetworkRequests.put(req, nri);
- if (req.isListen()) {
- for (final NetworkAgentInfo network : mNetworkAgentInfos) {
- if (req.networkCapabilities.hasSignalStrength()
- && network.satisfiesImmutableCapabilitiesOf(req)) {
- updateSignalStrengthThresholds(network, "REGISTER", req);
+ for (final NetworkRequestInfo nri : nris) {
+ mNetworkRequestInfoLogs.log("REGISTER " + nri);
+ for (final NetworkRequest req : nri.mRequests) {
+ mNetworkRequests.put(req, nri);
+ if (req.isListen()) {
+ for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+ if (req.networkCapabilities.hasSignalStrength()
+ && network.satisfiesImmutableCapabilitiesOf(req)) {
+ updateSignalStrengthThresholds(network, "REGISTER", req);
+ }
}
}
}
}
- rematchAllNetworksAndRequests();
- // If the nri is satisfied, return as its score has already been sent if needed.
- if (nri.isBeingSatisfied()) {
- return;
- }
- // As this request was not satisfied on rematch and thus never had any scores sent to the
- // factories, send null now for each request of type REQUEST.
- for (final NetworkRequest req : nri.mRequests) {
- if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+ rematchAllNetworksAndRequests();
+ for (final NetworkRequestInfo nri : nris) {
+ // If the nri is satisfied, return as its score has already been sent if needed.
+ if (nri.isBeingSatisfied()) {
+ return;
+ }
+
+ // As this request was not satisfied on rematch and thus never had any scores sent to
+ // the factories, send null now for each request of type REQUEST.
+ for (final NetworkRequest req : nri.mRequests) {
+ if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+ }
}
}
@@ -3781,6 +3788,7 @@
removeListenRequestFromNetworks(req);
}
}
+ mDefaultNetworkRequests.remove(nri);
mNetworkRequestCounter.decrementCount(nri.mUid);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
@@ -4419,6 +4427,16 @@
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
break;
+ case EVENT_SET_OEM_NETWORK_PREFERENCE:
+ final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
+ (Pair<OemNetworkPreferences,
+ IOnSetOemNetworkPreferenceListener>) msg.obj;
+ try {
+ handleSetOemNetworkPreference(arg.first, arg.second);
+ } catch (RemoteException e) {
+ loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+ }
+ break;
}
}
}
@@ -4721,183 +4739,6 @@
}
/**
- * Prepare for a VPN application.
- * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
- * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
- *
- * @param oldPackage Package name of the application which currently controls VPN, which will
- * be replaced. If there is no such application, this should should either be
- * {@code null} or {@link VpnConfig.LEGACY_VPN}.
- * @param newPackage Package name of the application which should gain control of VPN, or
- * {@code null} to disable.
- * @param userId User for whom to prepare the new VPN.
- *
- * @hide
- */
- @Override
- public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
- int userId) {
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- Vpn vpn = mVpns.get(userId);
- if (vpn != null) {
- return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
- } else {
- return false;
- }
- }
- }
-
- /**
- * Set whether the VPN package has the ability to launch VPNs without user intervention. This
- * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
- * class. If the caller is not {@code userId}, {@link
- * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
- *
- * @param packageName The package for which authorization state should change.
- * @param userId User for whom {@code packageName} is installed.
- * @param authorized {@code true} if this app should be able to start a VPN connection without
- * explicit user approval, {@code false} if not.
- * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
- * permissions should be granted. When unauthorizing an app, {@link
- * VpnManager.TYPE_VPN_NONE} should be used.
- * @hide
- */
- @Override
- public void setVpnPackageAuthorization(
- String packageName, int userId, @VpnManager.VpnType int vpnType) {
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn != null) {
- vpn.setPackageAuthorization(packageName, vpnType);
- }
- }
- }
-
- /**
- * Configure a TUN interface and return its file descriptor. Parameters
- * are encoded and opaque to this class. This method is used by VpnBuilder
- * and not available in ConnectivityManager. Permissions are checked in
- * Vpn class.
- * @hide
- */
- @Override
- public ParcelFileDescriptor establishVpn(VpnConfig config) {
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- return mVpns.get(user).establish(config);
- }
- }
-
- /**
- * Stores the given VPN profile based on the provisioning package name.
- *
- * <p>If there is already a VPN profile stored for the provisioning package, this call will
- * overwrite the profile.
- *
- * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
- * exclusively by the Settings app, and passed into the platform at startup time.
- *
- * @return {@code true} if user consent has already been granted, {@code false} otherwise.
- * @hide
- */
- @Override
- public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
- final int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
- }
- }
-
- /**
- * Deletes the stored VPN profile for the provisioning package
- *
- * <p>If there are no profiles for the given package, this method will silently succeed.
- *
- * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
- * exclusively by the Settings app, and passed into the platform at startup time.
- *
- * @hide
- */
- @Override
- public void deleteVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
- }
- }
-
- /**
- * Starts the VPN based on the stored profile for the given package
- *
- * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
- * exclusively by the Settings app, and passed into the platform at startup time.
- *
- * @throws IllegalArgumentException if no profile was found for the given package name.
- * @hide
- */
- @Override
- public void startVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- mVpns.get(user).startVpnProfile(packageName, mKeyStore);
- }
- }
-
- /**
- * Stops the Platform VPN if the provided package is running one.
- *
- * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
- * exclusively by the Settings app, and passed into the platform at startup time.
- *
- * @hide
- */
- @Override
- public void stopVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- mVpns.get(user).stopVpnProfile(packageName);
- }
- }
-
- /**
- * Start legacy VPN, controlling native daemons as needed. Creates a
- * secondary thread to perform connection work, returning quickly.
- */
- @Override
- public void startLegacyVpn(VpnProfile profile) {
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- final LinkProperties egress = getActiveLinkProperties();
- if (egress == null) {
- throw new IllegalStateException("Missing active network connection");
- }
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
- }
- }
-
- /**
- * Return the information of the ongoing legacy VPN. This method is used
- * by VpnSettings and not available in ConnectivityManager. Permissions
- * are checked in Vpn class.
- */
- @Override
- public LegacyVpnInfo getLegacyVpnInfo(int userId) {
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- return mVpns.get(userId).getLegacyVpnInfo();
- }
- }
-
- /**
* Return the information of all ongoing VPNs.
*
* <p>This method is used to update NetworkStatsService.
@@ -4906,10 +4747,8 @@
*/
private UnderlyingNetworkInfo[] getAllVpnInfo() {
ensureRunningOnConnectivityServiceThread();
- synchronized (mVpns) {
- if (mLockdownEnabled) {
- return new UnderlyingNetworkInfo[0];
- }
+ if (mLockdownEnabled) {
+ return new UnderlyingNetworkInfo[0];
}
List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
@@ -4965,25 +4804,6 @@
nai.linkProperties.getInterfaceName(), interfaces);
}
- /**
- * Returns the information of the ongoing VPN for {@code userId}. This method is used by
- * VpnDialogs and not available in ConnectivityManager.
- * Permissions are checked in Vpn class.
- * @hide
- */
- @Override
- public VpnConfig getVpnConfig(int userId) {
- enforceCrossUserPermission(userId);
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn != null) {
- return vpn.getVpnConfig();
- } else {
- return null;
- }
- }
- }
-
// TODO This needs to be the default network that applies to the NAI.
private Network[] underlyingNetworksOrDefault(final int ownerUid,
Network[] underlyingNetworks) {
@@ -5071,195 +4891,54 @@
mVpnBlockedUidRanges = newVpnBlockedUidRanges;
}
- private boolean isLockdownVpnEnabled() {
- return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
- }
-
@Override
- public boolean updateLockdownVpn() {
- // Allow the system UID for the system server and for Settings.
- // Also, for unit tests, allow the process that ConnectivityService is running in.
- if (mDeps.getCallingUid() != Process.SYSTEM_UID
- && Binder.getCallingPid() != Process.myPid()) {
- logw("Lockdown VPN only available to system process or AID_SYSTEM");
- return false;
- }
-
- synchronized (mVpns) {
- // Tear down existing lockdown if profile was removed
- mLockdownEnabled = isLockdownVpnEnabled();
- if (mLockdownEnabled) {
- byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
- if (profileTag == null) {
- loge("Lockdown VPN configured but cannot be read from keystore");
- return false;
- }
- String profileName = new String(profileTag);
- final VpnProfile profile = VpnProfile.decode(
- profileName, mKeyStore.get(Credentials.VPN + profileName));
- if (profile == null) {
- loge("Lockdown VPN configured invalid profile " + profileName);
- setLockdownTracker(null);
- return true;
- }
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- Vpn vpn = mVpns.get(user);
- if (vpn == null) {
- logw("VPN for user " + user + " not ready yet. Skipping lockdown");
- return false;
- }
- setLockdownTracker(
- new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn, profile));
- } else {
- setLockdownTracker(null);
- }
- }
-
- return true;
- }
-
- /**
- * Internally set new {@link LockdownVpnTracker}, shutting down any existing
- * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
- */
- @GuardedBy("mVpns")
- private void setLockdownTracker(LockdownVpnTracker tracker) {
- // Shutdown any existing tracker
- final LockdownVpnTracker existing = mLockdownTracker;
- // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
- // necessary onBlockedStatusChanged callbacks.
- mLockdownTracker = null;
- if (existing != null) {
- existing.shutdown();
- }
-
- if (tracker != null) {
- mLockdownTracker = tracker;
- mLockdownTracker.init();
- }
- }
-
- /**
- * Throws if there is any currently running, always-on Legacy VPN.
- *
- * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
- * running across the entire system. Tracking for app-based VPNs is done on a per-user,
- * per-package basis in Vpn.java
- */
- @GuardedBy("mVpns")
- private void throwIfLockdownEnabled() {
- if (mLockdownEnabled) {
- throw new IllegalStateException("Unavailable in lockdown mode");
- }
- }
-
- /**
- * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
- * some setup and then call {@code establish()} to connect.
- *
- * @return {@code true} if the service was started, the service was already connected, or there
- * was no always-on VPN to start. {@code false} otherwise.
- */
- private boolean startAlwaysOnVpn(int userId) {
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- // Shouldn't happen as all code paths that point here should have checked the Vpn
- // exists already.
- Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
- return false;
- }
-
- return vpn.startAlwaysOnVpn(mKeyStore);
- }
- }
-
- @Override
- public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+ public void setLegacyLockdownVpnEnabled(boolean enabled) {
enforceSettingsPermission();
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- logw("User " + userId + " has no Vpn configuration");
- return false;
- }
- return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
- }
+ mHandler.post(() -> mLockdownEnabled = enabled);
}
- @Override
- public boolean setAlwaysOnVpnPackage(
- int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
- enforceControlAlwaysOnVpnPermission();
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- // Can't set always-on VPN if legacy VPN is already in lockdown mode.
- if (isLockdownVpnEnabled()) {
- return false;
- }
-
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- logw("User " + userId + " has no Vpn configuration");
- return false;
- }
- if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
- return false;
- }
- if (!startAlwaysOnVpn(userId)) {
- vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
- return false;
- }
- }
- return true;
+ private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
+ return mLockdownEnabled
+ && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
+ && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
}
- @Override
- public String getAlwaysOnVpnPackage(int userId) {
- enforceControlAlwaysOnVpnPermission();
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- logw("User " + userId + " has no Vpn configuration");
- return null;
- }
- return vpn.getAlwaysOnPackage();
+ private NetworkAgentInfo getLegacyLockdownNai() {
+ if (!mLockdownEnabled) {
+ return null;
}
- }
+ // The legacy lockdown VPN always only applies to userId 0.
+ final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
+ if (nai == null || !isLegacyLockdownNai(nai)) return null;
- @Override
- public boolean isVpnLockdownEnabled(int userId) {
- enforceControlAlwaysOnVpnPermission();
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- logw("User " + userId + " has no Vpn configuration");
- return false;
- }
- return vpn.getLockdown();
+ // The legacy lockdown VPN must always have exactly one underlying network.
+ // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
+ // a local variable. There is no need to make a copy because its contents cannot change.
+ final Network[] underlying = nai.declaredUnderlyingNetworks;
+ if (underlying == null || underlying.length != 1) {
+ return null;
}
- }
- @Override
- public List<String> getVpnLockdownWhitelist(int userId) {
- enforceControlAlwaysOnVpnPermission();
- enforceCrossUserPermission(userId);
-
- synchronized (mVpns) {
- Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- logw("User " + userId + " has no Vpn configuration");
- return null;
- }
- return vpn.getLockdownAllowlist();
+ // The legacy lockdown VPN always uses the default network.
+ // If the VPN's underlying network is no longer the current default network, it means that
+ // the default network has just switched, and the VPN is about to disconnect.
+ // Report that the VPN is not connected, so when the state of NetworkInfo objects
+ // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED.
+ final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+ if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
+ return null;
}
+
+ return nai;
+ };
+
+ private DetailedState getLegacyLockdownState(DetailedState origState) {
+ if (origState != DetailedState.CONNECTED) {
+ return origState;
+ }
+ return (mLockdownEnabled && getLegacyLockdownNai() == null)
+ ? DetailedState.CONNECTING
+ : DetailedState.CONNECTED;
}
@Override
@@ -5294,111 +4973,12 @@
}
}
- private void onUserStarted(int userId) {
- synchronized (mVpns) {
- Vpn userVpn = mVpns.get(userId);
- if (userVpn != null) {
- loge("Starting user already has a VPN");
- return;
- }
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
- mVpns.put(userId, userVpn);
- if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
- updateLockdownVpn();
- }
- }
- }
-
- private void onUserStopped(int userId) {
- synchronized (mVpns) {
- Vpn userVpn = mVpns.get(userId);
- if (userVpn == null) {
- loge("Stopped user has no VPN");
- return;
- }
- userVpn.onUserStopped();
- mVpns.delete(userId);
- }
- }
-
private void onUserAdded(int userId) {
mPermissionMonitor.onUserAdded(userId);
- synchronized (mVpns) {
- final int vpnsSize = mVpns.size();
- for (int i = 0; i < vpnsSize; i++) {
- Vpn vpn = mVpns.valueAt(i);
- vpn.onUserAdded(userId);
- }
- }
}
private void onUserRemoved(int userId) {
mPermissionMonitor.onUserRemoved(userId);
- synchronized (mVpns) {
- final int vpnsSize = mVpns.size();
- for (int i = 0; i < vpnsSize; i++) {
- Vpn vpn = mVpns.valueAt(i);
- vpn.onUserRemoved(userId);
- }
- }
- }
-
- private void onPackageReplaced(String packageName, int uid) {
- if (TextUtils.isEmpty(packageName) || uid < 0) {
- Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
- return;
- }
- final int userId = UserHandle.getUserId(uid);
- synchronized (mVpns) {
- final Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- return;
- }
- // Legacy always-on VPN won't be affected since the package name is not set.
- if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
- log("Restarting always-on VPN package " + packageName + " for user "
- + userId);
- vpn.startAlwaysOnVpn(mKeyStore);
- }
- }
- }
-
- private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
- if (TextUtils.isEmpty(packageName) || uid < 0) {
- Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
- return;
- }
-
- final int userId = UserHandle.getUserId(uid);
- synchronized (mVpns) {
- final Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
- return;
- }
- // Legacy always-on VPN won't be affected since the package name is not set.
- if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
- log("Removing always-on VPN package " + packageName + " for user "
- + userId);
- vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
- }
- }
- }
-
- private void onUserUnlocked(int userId) {
- synchronized (mVpns) {
- // User present may be sent because of an unlock, which might mean an unlocked keystore.
- if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
- updateLockdownVpn();
- } else {
- startAlwaysOnVpn(userId);
- }
- }
- }
-
- private void onVpnLockdownReset() {
- synchronized (mVpns) {
- if (mLockdownTracker != null) mLockdownTracker.reset();
- }
}
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -5407,52 +4987,20 @@
ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
- final Uri packageData = intent.getData();
- final String packageName =
- packageData != null ? packageData.getSchemeSpecificPart() : null;
-
- if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
- onVpnLockdownReset();
- }
// UserId should be filled for below intents, check the existence.
if (userId == UserHandle.USER_NULL) return;
- if (Intent.ACTION_USER_STARTED.equals(action)) {
- onUserStarted(userId);
- } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- onUserStopped(userId);
- } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+ if (Intent.ACTION_USER_ADDED.equals(action)) {
onUserAdded(userId);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(userId);
- } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
- onUserUnlocked(userId);
- } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
- onPackageReplaced(packageName, uid);
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- final boolean isReplacing = intent.getBooleanExtra(
- Intent.EXTRA_REPLACING, false);
- onPackageRemoved(packageName, uid, isReplacing);
- } else {
+ } else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
- private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Try creating lockdown tracker, since user present usually means
- // unlocked keystore.
- updateLockdownVpn();
- // Use the same context that registered receiver before to unregister it. Because use
- // different context to unregister receiver will cause exception.
- context.unregisterReceiver(this);
- }
- };
-
private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
@@ -5551,10 +5099,12 @@
final PendingIntent mPendingIntent;
boolean mPendingIntentSent;
+ @Nullable
+ final Messenger mMessenger;
+ @Nullable
private final IBinder mBinder;
final int mPid;
final int mUid;
- final Messenger messenger;
@Nullable
final String mCallingAttributionTag;
@@ -5570,12 +5120,17 @@
return uids;
}
- NetworkRequestInfo(NetworkRequest r, PendingIntent pi,
+ NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
@Nullable String callingAttributionTag) {
+ this(Collections.singletonList(r), pi, callingAttributionTag);
+ }
+
+ NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+ @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
mRequests = initializeRequests(r);
ensureAllNetworkRequestsHaveType(mRequests);
mPendingIntent = pi;
- messenger = null;
+ mMessenger = null;
mBinder = null;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
@@ -5583,11 +5138,16 @@
mCallingAttributionTag = callingAttributionTag;
}
- NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder,
- @Nullable String callingAttributionTag) {
+ NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+ @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
+ this(Collections.singletonList(r), m, binder, callingAttributionTag);
+ }
+
+ NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m,
+ @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
super();
- messenger = m;
mRequests = initializeRequests(r);
+ mMessenger = m;
ensureAllNetworkRequestsHaveType(mRequests);
mBinder = binder;
mPid = getCallingPid();
@@ -5603,7 +5163,11 @@
}
}
- NetworkRequestInfo(NetworkRequest r) {
+ NetworkRequestInfo(@NonNull final NetworkRequest r) {
+ this(Collections.singletonList(r));
+ }
+
+ NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
this(r, null /* pi */, null /* callingAttributionTag */);
}
@@ -5618,9 +5182,10 @@
return mRequests.size() > 1;
}
- private List<NetworkRequest> initializeRequests(NetworkRequest r) {
- final ArrayList<NetworkRequest> tempRequests = new ArrayList<>();
- tempRequests.add(new NetworkRequest(r));
+ private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) {
+ // Creating a defensive copy to prevent the sender from modifying the list being
+ // reflected in the return value of this method.
+ final List<NetworkRequest> tempRequests = new ArrayList<>(r);
return Collections.unmodifiableList(tempRequests);
}
@@ -5804,7 +5369,7 @@
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
NetworkRequestInfo nri =
- new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+ new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
if (DBG) log("requestNetwork for " + nri);
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5813,7 +5378,8 @@
// changes don't alter request matching.
if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
(!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
- Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+ throw new IllegalStateException(
+ "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+ networkCapabilities + " vs. " + defaultNc);
}
@@ -5970,7 +5536,7 @@
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
- new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+ new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
if (VDBG) log("listenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -6098,13 +5664,20 @@
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
+ // Current OEM network preferences.
+ @NonNull
+ private OemNetworkPreferences mOemNetworkPreferences =
+ new OemNetworkPreferences.Builder().build();
+
// The always-on request for an Internet-capable network that apps without a specific default
// fall back to.
+ @VisibleForTesting
@NonNull
- private final NetworkRequestInfo mDefaultRequest;
+ final NetworkRequestInfo mDefaultRequest;
// Collection of NetworkRequestInfo's used for default networks.
+ @VisibleForTesting
@NonNull
- private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
+ final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
@@ -7181,7 +6754,7 @@
private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
@NonNull final NetworkAgentInfo networkAgent, final int notificationType,
final int arg1) {
- if (nri.messenger == null) {
+ if (nri.mMessenger == null) {
// Default request has no msgr. Also prevents callbacks from being invoked for
// NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
@@ -7250,7 +6823,7 @@
String notification = ConnectivityManager.getCallbackName(notificationType);
log("sending notification " + notification + " for " + nrForCallback);
}
- nri.messenger.send(msg);
+ nri.mMessenger.send(msg);
} catch (RemoteException e) {
// may occur naturally in the race of binder death.
loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
@@ -7339,7 +6912,6 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
- notifyLockdownVpn(newDefaultNetwork);
handleApplyDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
updateTcpBufferSizes(null != newDefaultNetwork
@@ -7797,12 +7369,6 @@
mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
mLegacyTypeTracker.add(
newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
- // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
- // to reflect the NetworkInfo of this new network. This broadcast has to be sent
- // after the disconnect broadcasts above, but before the broadcasts sent by the
- // legacy type tracker below.
- // TODO : refactor this, it's too complex
- notifyLockdownVpn(newDefaultNetwork);
}
}
@@ -7860,18 +7426,6 @@
sendInetConditionBroadcast(nai.networkInfo);
}
- private void notifyLockdownVpn(NetworkAgentInfo nai) {
- synchronized (mVpns) {
- if (mLockdownTracker != null) {
- if (nai != null && nai.isVPN()) {
- mLockdownTracker.onVpnStateChanged(nai.networkInfo);
- } else {
- mLockdownTracker.onNetworkInfoChanged();
- }
- }
- }
- }
-
@NonNull
private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
final NetworkInfo newInfo = new NetworkInfo(info);
@@ -7910,7 +7464,6 @@
oldInfo = networkAgent.networkInfo;
networkAgent.networkInfo = newInfo;
}
- notifyLockdownVpn(networkAgent);
if (DBG) {
log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
@@ -8211,34 +7764,6 @@
}
@Override
- public boolean addVpnAddress(String address, int prefixLength) {
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- return mVpns.get(user).addAddress(address, prefixLength);
- }
- }
-
- @Override
- public boolean removeVpnAddress(String address, int prefixLength) {
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- synchronized (mVpns) {
- throwIfLockdownEnabled();
- return mVpns.get(user).removeAddress(address, prefixLength);
- }
- }
-
- @Override
- public boolean setUnderlyingNetworksForVpn(Network[] networks) {
- int user = UserHandle.getUserId(mDeps.getCallingUid());
- final boolean success;
- synchronized (mVpns) {
- success = mVpns.get(user).setUnderlyingNetworks(networks);
- }
- return success;
- }
-
- @Override
public String getCaptivePortalServerUrl() {
enforceNetworkStackOrSettingsPermission();
String settingUrl = mContext.getResources().getString(
@@ -8317,8 +7842,6 @@
return;
}
- final int userId = UserHandle.getCallingUserId();
-
final long token = Binder.clearCallingIdentity();
try {
final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
@@ -8330,44 +7853,6 @@
// Turn airplane mode off
setAirplaneMode(false);
- if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
- // Remove always-on package
- synchronized (mVpns) {
- final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
- if (alwaysOnPackage != null) {
- setAlwaysOnVpnPackage(userId, null, false, null);
- setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
- }
-
- // Turn Always-on VPN off
- if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
- final long ident = Binder.clearCallingIdentity();
- try {
- mKeyStore.delete(Credentials.LOCKDOWN_VPN);
- mLockdownEnabled = false;
- setLockdownTracker(null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- // Turn VPN off
- VpnConfig vpnConfig = getVpnConfig(userId);
- if (vpnConfig != null) {
- if (vpnConfig.legacy) {
- prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
- } else {
- // Prevent this app (packagename = vpnConfig.user) from initiating
- // VPN connections in the future without user intervention.
- setVpnPackageAuthorization(
- vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
-
- prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
- }
- }
- }
- }
-
// restore private DNS settings to default mode (opportunistic)
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
Settings.Global.putString(mContext.getContentResolver(),
@@ -8459,25 +7944,6 @@
}
}
- @GuardedBy("mVpns")
- private Vpn getVpnIfOwner() {
- return getVpnIfOwner(mDeps.getCallingUid());
- }
-
- // TODO: stop calling into Vpn.java and get this information from data in this class.
- @GuardedBy("mVpns")
- private Vpn getVpnIfOwner(int uid) {
- final int user = UserHandle.getUserId(uid);
-
- final Vpn vpn = mVpns.get(user);
- if (vpn == null) {
- return null;
- } else {
- final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
- return (info == null || info.ownerUid != uid) ? null : vpn;
- }
- }
-
private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
if (vpn == null) return VpnManager.TYPE_VPN_NONE;
final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
@@ -8514,22 +7980,6 @@
return uid;
}
- @Override
- public boolean isCallerCurrentAlwaysOnVpnApp() {
- synchronized (mVpns) {
- Vpn vpn = getVpnIfOwner();
- return vpn != null && vpn.getAlwaysOn();
- }
- }
-
- @Override
- public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
- synchronized (mVpns) {
- Vpn vpn = getVpnIfOwner();
- return vpn != null && vpn.getLockdown();
- }
- }
-
/**
* Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
*
@@ -9205,9 +8655,212 @@
mQosCallbackTracker.unregisterCallback(callback);
}
+ private void enforceAutomotiveDevice() {
+ final boolean isAutomotiveDevice =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ if (!isAutomotiveDevice) {
+ throw new UnsupportedOperationException(
+ "setOemNetworkPreference() is only available on automotive devices.");
+ }
+ }
+
+ /**
+ * Used by automotive devices to set the network preferences used to direct traffic at an
+ * application level as per the given OemNetworkPreferences. An example use-case would be an
+ * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+ * vehicle via a particular network.
+ *
+ * Calling this will overwrite the existing preference.
+ *
+ * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+ * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used
+ * to communicate completion of setOemNetworkPreference();
+ */
@Override
- public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
- // TODO http://b/176495594 track multiple default networks with networkPreferences
- if (DBG) log("setOemNetworkPreference() called with: " + preference.toString());
+ public void setOemNetworkPreference(
+ @NonNull final OemNetworkPreferences preference,
+ @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+
+ enforceAutomotiveDevice();
+ enforceOemNetworkPreferencesPermission();
+
+ Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+ validateOemNetworkPreferences(preference);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
+ new Pair<>(preference, listener)));
+ }
+
+ private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) {
+ for (@OemNetworkPreferences.OemNetworkPreference final int pref
+ : preference.getNetworkPreferences().values()) {
+ if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) {
+ final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value.";
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ }
+
+ private void handleSetOemNetworkPreference(
+ @NonNull final OemNetworkPreferences preference,
+ @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+ Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+ if (DBG) {
+ log("set OEM network preferences :" + preference.toString());
+ }
+ final List<NetworkRequestInfo> nris =
+ new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
+ updateDefaultNetworksForOemNetworkPreference(nris);
+ mOemNetworkPreferences = preference;
+ // TODO http://b/176496396 persist data to shared preferences.
+
+ if (null != listener) {
+ listener.onComplete();
+ }
+ }
+
+ private void updateDefaultNetworksForOemNetworkPreference(
+ @NonNull final List<NetworkRequestInfo> nris) {
+ ensureRunningOnConnectivityServiceThread();
+ clearNonDefaultNetworkAgents();
+ addDefaultNetworkRequests(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]);
+ for (final NetworkRequestInfo nri : nris) {
+ if (mDefaultRequest != nri) {
+ handleRemoveNetworkRequest(nri);
+ }
+ }
+ }
+
+ private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) {
+ mDefaultNetworkRequests.addAll(nris);
+ handleRegisterNetworkRequest(nris);
+ }
+
+ /**
+ * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
+ */
+ @VisibleForTesting
+ final class OemNetworkRequestFactory {
+ List<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+ @NonNull final OemNetworkPreferences preference) {
+ final List<NetworkRequestInfo> nris = new ArrayList<>();
+ final SparseArray<Set<Integer>> uids =
+ createUidsFromOemNetworkPreferences(preference);
+ for (int i = 0; i < uids.size(); i++) {
+ final int key = uids.keyAt(i);
+ final Set<Integer> value = uids.valueAt(i);
+ final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value);
+ // No need to add an nri without any requests.
+ if (0 == nri.mRequests.size()) {
+ continue;
+ }
+ nris.add(nri);
+ }
+
+ return nris;
+ }
+
+ private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
+ @NonNull final OemNetworkPreferences preference) {
+ final SparseArray<Set<Integer>> uids = new SparseArray<>();
+ final PackageManager pm = mContext.getPackageManager();
+ for (final Map.Entry<String, Integer> entry :
+ preference.getNetworkPreferences().entrySet()) {
+ @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
+ try {
+ final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid;
+ if (!uids.contains(pref)) {
+ uids.put(pref, new ArraySet<>());
+ }
+ uids.get(pref).add(uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Although this may seem like an error scenario, it is ok that uninstalled
+ // packages are sent on a network preference as the system will watch for
+ // package installations associated with this network preference and update
+ // accordingly. This is done so as to minimize race conditions on app install.
+ // TODO b/177092163 add app install watching.
+ continue;
+ }
+ }
+ return uids;
+ }
+
+ private NetworkRequestInfo createNriFromOemNetworkPreferences(
+ @OemNetworkPreferences.OemNetworkPreference final int preference,
+ @NonNull final Set<Integer> uids) {
+ final List<NetworkRequest> requests = new ArrayList<>();
+ // Requests will ultimately be evaluated by order of insertion therefore it matters.
+ switch (preference) {
+ case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID:
+ requests.add(createUnmeteredNetworkRequest());
+ requests.add(createOemPaidNetworkRequest());
+ requests.add(createDefaultRequest());
+ break;
+ case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
+ requests.add(createUnmeteredNetworkRequest());
+ requests.add(createOemPaidNetworkRequest());
+ break;
+ case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY:
+ requests.add(createOemPaidNetworkRequest());
+ break;
+ case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY:
+ requests.add(createOemPrivateNetworkRequest());
+ break;
+ default:
+ // This should never happen.
+ throw new IllegalArgumentException("createNriFromOemNetworkPreferences()"
+ + " called with invalid preference of " + preference);
+ }
+
+ setOemNetworkRequestUids(requests, uids);
+ return new NetworkRequestInfo(requests);
+ }
+
+ private NetworkRequest createUnmeteredNetworkRequest() {
+ final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_VALIDATED);
+ return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap);
+ }
+
+ private NetworkRequest createOemPaidNetworkRequest() {
+ // NET_CAPABILITY_OEM_PAID is a restricted capability.
+ final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+ .addCapability(NET_CAPABILITY_OEM_PAID)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+ }
+
+ private NetworkRequest createOemPrivateNetworkRequest() {
+ // NET_CAPABILITY_OEM_PRIVATE is a restricted capability.
+ final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+ .addCapability(NET_CAPABILITY_OEM_PRIVATE)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+ }
+
+ private NetworkCapabilities createDefaultPerAppNetCap() {
+ final NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+ return netCap;
+ }
+
+ private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
+ @NonNull final Set<Integer> uids) {
+ final Set<UidRange> ranges = new ArraySet<>();
+ for (final int uid : uids) {
+ ranges.add(new UidRange(uid, uid));
+ }
+ for (final NetworkRequest req : requests) {
+ req.networkCapabilities.setUids(ranges);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index f648c3e..b48bc90 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -29,6 +29,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
import android.net.IIpSecService;
import android.net.INetd;
import android.net.InetAddresses;
@@ -41,6 +42,7 @@
import android.net.IpSecTunnelInterfaceResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.TrafficStats;
import android.net.util.NetdService;
@@ -797,9 +799,15 @@
}
}
- private final class TunnelInterfaceRecord extends OwnedResourceRecord {
+ /**
+ * Tracks an tunnel interface, and manages cleanup paths.
+ *
+ * <p>This class is not thread-safe, and expects that that users of this class will ensure
+ * synchronization and thread safety by holding the IpSecService.this instance lock
+ */
+ @VisibleForTesting
+ final class TunnelInterfaceRecord extends OwnedResourceRecord {
private final String mInterfaceName;
- private final Network mUnderlyingNetwork;
// outer addresses
private final String mLocalAddress;
@@ -810,6 +818,8 @@
private final int mIfId;
+ private Network mUnderlyingNetwork;
+
TunnelInterfaceRecord(
int resourceId,
String interfaceName,
@@ -870,14 +880,22 @@
releaseNetId(mOkey);
}
- public String getInterfaceName() {
- return mInterfaceName;
+ @GuardedBy("IpSecService.this")
+ public void setUnderlyingNetwork(Network underlyingNetwork) {
+ // When #applyTunnelModeTransform is called, this new underlying network will be used to
+ // update the output mark of the input transform.
+ mUnderlyingNetwork = underlyingNetwork;
}
+ @GuardedBy("IpSecService.this")
public Network getUnderlyingNetwork() {
return mUnderlyingNetwork;
}
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
/** Returns the local, outer address for the tunnelInterface */
public String getLocalAddress() {
return mLocalAddress;
@@ -1429,6 +1447,34 @@
}
}
+ /** Set TunnelInterface to use a specific underlying network. */
+ @Override
+ public synchronized void setNetworkForTunnelInterface(
+ int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+ final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ final ConnectivityManager connectivityManager =
+ mContext.getSystemService(ConnectivityManager.class);
+ final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+ throw new IllegalArgumentException(
+ "Underlying network cannot be the network being exposed by this tunnel");
+ }
+
+ // It is meaningless to check if the network exists or is valid because the network might
+ // disconnect at any time after it passes the check.
+
+ tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+ }
+
/**
* Delete a TunnelInterface that has been been allocated by and registered with the system
* server
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a6cfae4..e12586b 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,8 +1,8 @@
# Connectivity / Networking
per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
-# Vibrator / Threads
-per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+# Threads
+per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
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/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
import android.os.UserManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.R;
@@ -147,14 +148,15 @@
private int getAllowedUid(int userHandle) {
String allowedPackage = mContext.getResources()
.getString(R.string.config_persistentDataPackageName);
- PackageManager pm = mContext.getPackageManager();
int allowedUid = -1;
- try {
- allowedUid = pm.getPackageUidAsUser(allowedPackage,
- PackageManager.MATCH_SYSTEM_ONLY, userHandle);
- } catch (PackageManager.NameNotFoundException e) {
- // not expected
- Slog.e(TAG, "not able to find package " + allowedPackage, e);
+ if (!TextUtils.isEmpty(allowedPackage)) {
+ try {
+ allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+ allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ // not expected
+ Slog.e(TAG, "not able to find package " + allowedPackage, e);
+ }
}
return allowedUid;
}
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1ad0176..2f98199 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -939,9 +939,12 @@
if (transcodeEnabled) {
LocalServices.getService(ActivityManagerInternal.class)
.registerAnrController((packageName, uid) -> {
- // TODO: Retrieve delay from ExternalStorageService that can check
- // transcoding status
- return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0);
+ try {
+ return mStorageSessionController.getAnrDelayMillis(packageName, uid);
+ } catch (ExternalStorageServiceException e) {
+ Log.e(TAG, "Failed to get ANR delay for " + packageName, e);
+ return 0;
+ }
});
}
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 7f638b9..915517a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -19,6 +19,7 @@
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
@@ -82,6 +83,7 @@
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -158,6 +160,7 @@
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
+ private ActivityTaskManagerInternal mActivityTaskManager;
private AlarmManager mAlarmManager;
private PowerManager mPowerManager;
@@ -366,6 +369,7 @@
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
TwilightManager twilightManager = getLocalService(TwilightManager.class);
if (twilightManager != null) mTwilightManager = twilightManager;
@@ -750,6 +754,39 @@
}
@Override
+ public void setApplicationNightMode(@UiModeManager.NightMode int mode)
+ throws RemoteException {
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_YES:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ case UiModeManager.MODE_NIGHT_CUSTOM:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+ final int configNightMode;
+ switch (mode) {
+ case MODE_NIGHT_YES:
+ configNightMode = Configuration.UI_MODE_NIGHT_YES;
+ break;
+ case MODE_NIGHT_NO:
+ configNightMode = Configuration.UI_MODE_NIGHT_NO;
+ break;
+ default:
+ configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
+ }
+ try {
+ final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+ mActivityTaskManager.createPackageConfigurationUpdater();
+ updater.setNightMode(configNightMode);
+ updater.commit();
+ } catch (RemoteException e) {
+ throw e;
+ }
+ }
+
+ @Override
public boolean isUiModeLocked() {
synchronized (mLock) {
return mUiModeLocked;
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
deleted file mode 100644
index 2ac365d..0000000
--- a/services/core/java/com/android/server/VibratorService.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.CombinedVibrationEffect;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/** System implementation of {@link IVibratorService}. */
-public class VibratorService extends IVibratorService.Stub {
- private static final String TAG = "VibratorService";
- private static final boolean DEBUG = false;
- private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-
- // Default vibration attributes. Used when vibration is requested without attributes
- private static final VibrationAttributes DEFAULT_ATTRIBUTES =
- new VibrationAttributes.Builder().build();
-
- // Used to generate globally unique vibration ids.
- private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
- private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousVibrations;
- private final int mPreviousVibrationsLimit;
- private final Handler mH;
- private final Object mLock = new Object();
- private final VibratorController mVibratorController;
- private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
-
- private final Context mContext;
- private final PowerManager.WakeLock mWakeLock;
- private final AppOpsManager mAppOps;
- private final IBatteryStats mBatteryStatsService;
- private final String mSystemUiPackage;
- private VibrationSettings mVibrationSettings;
- private VibrationScaler mVibrationScaler;
- private InputDeviceDelegate mInputDeviceDelegate;
-
- @GuardedBy("mLock")
- private VibrationThread mThread;
- @GuardedBy("mLock")
- private VibrationThread mNextVibrationThread;
-
- @GuardedBy("mLock")
- private Vibration mCurrentVibration;
- private int mCurVibUid = -1;
- private ExternalVibrationHolder mCurrentExternalVibration;
-
- /**
- * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished
- * vibrations.
- */
- private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
-
- @Override
- public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
- return false;
- }
-
- @Override
- public boolean triggerSyncedVibration(long vibrationId) {
- return false;
- }
-
- @Override
- public void cancelSyncedVibration() {
- }
-
- @Override
- public void onVibrationEnded(long vibrationId, Vibration.Status status) {
- if (DEBUG) {
- Slog.d(TAG, "Vibration thread finished with status " + status);
- }
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
- mThread = null;
- reportFinishVibrationLocked(status);
- if (mNextVibrationThread != null) {
- startVibrationThreadLocked(mNextVibrationThread);
- mNextVibrationThread = null;
- }
- }
- }
- }
- }
-
- /**
- * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service.
- */
- private static final class VibrationCompleteListener implements OnVibrationCompleteListener {
- private WeakReference<VibratorService> mServiceRef;
-
- VibrationCompleteListener(VibratorService service) {
- mServiceRef = new WeakReference<>(service);
- }
-
- @Override
- public void onComplete(int vibratorId, long vibrationId) {
- VibratorService service = mServiceRef.get();
- if (service != null) {
- service.onVibrationComplete(vibratorId, vibrationId);
- }
- }
- }
-
- /** Holder for a {@link ExternalVibration}. */
- private final class ExternalVibrationHolder {
-
- public final ExternalVibration externalVibration;
- public int scale;
-
- private final long mStartTimeDebug;
- private long mEndTimeDebug;
- private Vibration.Status mStatus;
-
- private ExternalVibrationHolder(ExternalVibration externalVibration) {
- this.externalVibration = externalVibration;
- this.scale = IExternalVibratorService.SCALE_NONE;
- mStartTimeDebug = System.currentTimeMillis();
- mStatus = Vibration.Status.RUNNING;
- }
-
- public void end(Vibration.Status status) {
- if (mStatus != Vibration.Status.RUNNING) {
- // Vibration already ended, keep first ending status set and ignore this one.
- return;
- }
- mStatus = status;
- mEndTimeDebug = System.currentTimeMillis();
- }
-
- public Vibration.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
- scale, externalVibration.getVibrationAttributes(),
- externalVibration.getUid(), externalVibration.getPackage(),
- /* reason= */ null, mStatus);
- }
- }
-
- VibratorService(Context context) {
- this(context, new Injector());
- }
-
- @VisibleForTesting
- VibratorService(Context context, Injector injector) {
- mH = injector.createHandler(Looper.myLooper());
- mVibratorController = injector.createVibratorController(
- new VibrationCompleteListener(this));
-
- // Reset the hardware to a default state, in case this is a runtime
- // restart instead of a fresh boot.
- mVibratorController.off();
-
- mContext = context;
- PowerManager pm = context.getSystemService(PowerManager.class);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
- mWakeLock.setReferenceCounted(true);
-
- mAppOps = mContext.getSystemService(AppOpsManager.class);
- mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
- BatteryStats.SERVICE_NAME));
- mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
- .getSystemUiServiceComponent().getPackageName();
-
- mPreviousVibrationsLimit = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-
- mPreviousRingVibrations = new LinkedList<>();
- mPreviousNotificationVibrations = new LinkedList<>();
- mPreviousAlarmVibrations = new LinkedList<>();
- mPreviousVibrations = new LinkedList<>();
- mPreviousExternalVibrations = new LinkedList<>();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- context.registerReceiver(mIntentReceiver, filter);
-
- injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
- }
-
- public void systemReady() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
- try {
- mVibrationSettings = new VibrationSettings(mContext, mH);
- mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
- mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
-
- mVibrationSettings.addListener(this::updateVibrators);
-
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mVibrationSettings.updateSettings();
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
-
- updateVibrators();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- /** Callback for when vibration is complete, to be called by native. */
- @VisibleForTesting
- public void onVibrationComplete(int vibratorId, long vibrationId) {
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
- && mThread != null) {
- if (DEBUG) {
- Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
- }
- // Let the thread playing the vibration handle the callback, since it might be
- // expecting the vibrator to turn off multiple times during a single vibration.
- mThread.vibratorComplete(vibratorId);
- }
- }
- }
-
- @Override // Binder call
- public boolean hasVibrator() {
- // For now, we choose to ignore the presence of input devices that have vibrators
- // when reporting whether the device has a vibrator. Applications often use this
- // information to decide whether to enable certain features so they expect the
- // result of hasVibrator() to be constant. For now, just report whether
- // the device has a built-in vibrator.
- return mVibratorController.isAvailable();
- }
-
- @Override // Binder call
- public boolean isVibrating() {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.isVibrating();
- }
-
- @Override // Binder call
- public VibratorInfo getVibratorInfo() {
- return mVibratorController.getVibratorInfo();
- }
-
- @Override // Binder call
- public boolean registerVibratorStateListener(IVibratorStateListener listener) {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.registerVibratorStateListener(listener);
- }
-
- @Override // Binder call
- @GuardedBy("mLock")
- public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.unregisterVibratorStateListener(listener);
- }
-
- @Override // Binder call
- public boolean hasAmplitudeControl() {
- // Input device vibrators always support amplitude controls.
- return mInputDeviceDelegate.isAvailable()
- || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
- }
-
- private void verifyIncomingUid(int uid) {
- if (uid == Binder.getCallingUid()) {
- return;
- }
- if (Binder.getCallingPid() == Process.myPid()) {
- return;
- }
- mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
-
- /**
- * Validate the incoming VibrationEffect.
- *
- * We can't throw exceptions here since we might be called from some system_server component,
- * which would bring the whole system down.
- *
- * @return whether the VibrationEffect is valid
- */
- private static boolean verifyVibrationEffect(VibrationEffect effect) {
- if (effect == null) {
- // Effect must not be null.
- Slog.wtf(TAG, "effect must not be null");
- return false;
- }
- try {
- effect.validate();
- } catch (Exception e) {
- Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
- return false;
- }
- return true;
- }
-
- private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked
- && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
- return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
- fallback);
- }
- return effect;
- }
-
- private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
- if (attrs == null) {
- attrs = DEFAULT_ATTRIBUTES;
- }
- if (shouldBypassDnd(attrs)) {
- if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getFlags()
- & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new VibrationAttributes.Builder(attrs)
- .setFlags(flags, attrs.getFlags()).build();
- }
- }
-
- return attrs;
- }
-
- @Override // Binder call
- public void vibrate(int uid, String opPkg, VibrationEffect effect,
- @Nullable VibrationAttributes attrs, String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
- try {
- if (!hasPermission(android.Manifest.permission.VIBRATE)) {
- throw new SecurityException("Requires VIBRATE permission");
- }
- if (token == null) {
- Slog.e(TAG, "token must not be null");
- return;
- }
- verifyIncomingUid(uid);
- if (!verifyVibrationEffect(effect)) {
- return;
- }
- effect = fixupVibrationEffect(effect);
- attrs = fixupVibrationAttributes(attrs);
- Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(),
- CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason);
-
- // If our current vibration is longer than the new vibration and is the same amplitude,
- // then just let the current one finish.
- synchronized (mLock) {
- VibrationEffect currentEffect =
- mCurrentVibration == null ? null : getEffect(mCurrentVibration);
- if (effect instanceof VibrationEffect.OneShot
- && currentEffect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
- VibrationEffect.OneShot currentOneShot =
- (VibrationEffect.OneShot) currentEffect;
- if (currentOneShot.getDuration() > newOneShot.getDuration()
- && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
- if (DEBUG) {
- Slog.d(TAG,
- "Ignoring incoming vibration in favor of current vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING);
- return;
- }
- }
-
-
- // If something has external control of the vibrator, assume that it's more
- // important for now.
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL);
- return;
- }
-
- // If the current vibration is repeating and the incoming one is non-repeating,
- // then ignore the non-repeating vibration. This is so that we don't cancel
- // vibrations that are meant to grab the attention of the user, like ringtones and
- // alarms, in favor of one-shot vibrations that are likely quite short.
- if (!isRepeatingVibration(effect)
- && mCurrentVibration != null
- && isRepeatingVibration(currentEffect)) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM);
- return;
- }
-
- if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) {
- Slog.e(TAG, "Ignoring incoming vibration as process with"
- + " uid= " + uid + " is background,"
- + " attrs= " + vib.attrs);
- endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND);
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- startVibrationLocked(vib);
- boolean isNextVibration = mNextVibrationThread != null
- && vib.equals(mNextVibrationThread.getVibration());
-
- if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
- // Vibration was unexpectedly ignored: add to list for debugging
- endVibrationLocked(vib, Vibration.Status.IGNORED);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private boolean hasPermission(String permission) {
- return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private static boolean isRepeatingVibration(VibrationEffect effect) {
- return effect.getDuration() == Long.MAX_VALUE;
- }
-
- private static <T extends VibrationEffect> T getEffect(Vibration vib) {
- return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect();
- }
-
- private void endVibrationLocked(Vibration vib, Vibration.Status status) {
- final LinkedList<Vibration.DebugInfo> previousVibrations;
- switch (vib.attrs.getUsage()) {
- case VibrationAttributes.USAGE_NOTIFICATION:
- previousVibrations = mPreviousNotificationVibrations;
- break;
- case VibrationAttributes.USAGE_RINGTONE:
- previousVibrations = mPreviousRingVibrations;
- break;
- case VibrationAttributes.USAGE_ALARM:
- previousVibrations = mPreviousAlarmVibrations;
- break;
- default:
- previousVibrations = mPreviousVibrations;
- }
- if (previousVibrations.size() > mPreviousVibrationsLimit) {
- previousVibrations.removeFirst();
- }
- vib.end(status);
- previousVibrations.addLast(vib.getDebugInfo());
- }
-
- private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
- if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
- mPreviousExternalVibrations.removeFirst();
- }
- vib.end(status);
- mPreviousExternalVibrations.addLast(vib.getDebugInfo());
- }
-
- @Override // Binder call
- public void cancelVibrate(IBinder token) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.VIBRATE,
- "cancelVibrate");
-
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.token == token) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
- }
-
- @GuardedBy("mLock")
- private void doCancelVibrateLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
- try {
- if (mThread != null) {
- mThread.cancel();
- }
- mInputDeviceDelegate.cancelVibrateIfAvailable();
- if (mCurrentExternalVibration != null) {
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.mute();
- mCurrentExternalVibration = null;
- mVibratorController.setExternalControl(false);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationLocked(final Vibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
- try {
- if (!shouldVibrate(vib)) {
- return;
- }
- applyVibrationIntensityScalingLocked(vib);
- startVibrationInnerLocked(vib);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationInnerLocked(Vibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
- try {
- boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
- vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
- if (inputDevicesAvailable) {
- endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
- } else if (mThread == null) {
- startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
- mBatteryStatsService, mVibrationCallbacks));
- } else {
- mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
- mBatteryStatsService, mVibrationCallbacks);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationThreadLocked(VibrationThread thread) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration = thread.getVibration();
- mThread = thread;
- mThread.start();
- }
-
- /** Scale the vibration effect by the intensity as appropriate based its intent. */
- private void applyVibrationIntensityScalingLocked(Vibration vib) {
- vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
- }
-
- private static boolean shouldBypassDnd(VibrationAttributes attrs) {
- return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
- }
-
- private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
- int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
- attrs.getAudioUsage(), uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
- }
-
- if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
- // If we're just ignoring the vibration op then this is set by DND and we should ignore
- // if we're asked to bypass. AppOps won't be able to record this operation, so make
- // sure we at least note it in the logs for debugging.
- Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
- mode = AppOpsManager.MODE_ALLOWED;
- }
- return mode;
- }
-
- private boolean shouldVibrate(Vibration vib) {
- if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) {
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER);
- return false;
- }
-
- int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage());
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS);
- return false;
- }
-
- if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE);
- return false;
- }
-
- final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (mode == AppOpsManager.MODE_ERRORED) {
- // We might be getting calls from within system_server, so we don't actually
- // want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
- endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS);
- }
- return false;
- }
-
- return true;
- }
-
- @GuardedBy("mLock")
- private void reportFinishVibrationLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
- try {
- if (mCurrentVibration != null) {
- endVibrationLocked(mCurrentVibration, status);
- mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
- mCurrentVibration.opPkg);
-
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration = null;
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @VisibleForTesting
- void updateVibrators() {
- synchronized (mLock) {
- boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
- mVibrationSettings.shouldVibrateInputDevices());
-
- if (mCurrentVibration == null) {
- return;
- }
-
- if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
- mCurrentVibration.attrs.getUsage())) {
- // If the state changes out from under us then just reset.
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
-
- private boolean isSystemHapticFeedback(Vibration vib) {
- if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
- return false;
- }
- return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
- }
-
- private void dumpInternal(PrintWriter pw) {
- pw.println("Vibrator Service:");
- synchronized (mLock) {
- pw.print(" mCurrentVibration=");
- if (mCurrentVibration != null) {
- pw.println(mCurrentVibration.getDebugInfo().toString());
- } else {
- pw.println("null");
- }
- pw.print(" mCurrentExternalVibration=");
- if (mCurrentExternalVibration != null) {
- pw.println(mCurrentExternalVibration.getDebugInfo().toString());
- } else {
- pw.println("null");
- }
- pw.println(" mVibratorController=" + mVibratorController);
- pw.println(" mVibrationSettings=" + mVibrationSettings);
- pw.println();
- pw.println(" Previous ring vibrations:");
- for (Vibration.DebugInfo info : mPreviousRingVibrations) {
- pw.print(" ");
- pw.println(info.toString());
- }
-
- pw.println(" Previous notification vibrations:");
- for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous alarm vibrations:");
- for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous vibrations:");
- for (Vibration.DebugInfo info : mPreviousVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous external vibrations:");
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- pw.println(" " + info);
- }
- }
- }
-
- private void dumpProto(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
- synchronized (mLock) {
- if (mCurrentVibration != null) {
- mCurrentVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_VIBRATION);
- }
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
- }
- proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
- proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- mVibratorController.isUnderExternalControl());
- mVibrationSettings.dumpProto(proto);
-
- for (Vibration.DebugInfo info : mPreviousRingVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
- }
- }
- proto.flush();
- }
-
- /** Point of injection for test dependencies */
- @VisibleForTesting
- static class Injector {
-
- VibratorController createVibratorController(OnVibrationCompleteListener listener) {
- return new VibratorController(/* vibratorId= */ -1, listener);
- }
-
- Handler createHandler(Looper looper) {
- return new Handler(looper);
- }
-
- void addService(String name, IBinder service) {
- ServiceManager.addService(name, service);
- }
- }
-
- BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mLock) {
- // When the system is entering a non-interactive state, we want
- // to cancel vibrations in case a misbehaving app has abandoned
- // them. However it may happen that the system is currently playing
- // haptic feedback as part of the transition. So we don't cancel
- // system vibrations.
- if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
- }
- };
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- final long ident = Binder.clearCallingIdentity();
-
- boolean isDumpProto = false;
- for (String arg : args) {
- if (arg.equals("--proto")) {
- isDumpProto = true;
- }
- }
- try {
- if (isDumpProto) {
- dumpProto(fd);
- } else {
- dumpInternal(pw);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
- }
-
- final class ExternalVibratorService extends IExternalVibratorService.Stub {
- ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
-
- @Override
- public int onExternalVibrationStart(ExternalVibration vib) {
- if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
- return IExternalVibratorService.SCALE_MUTE;
- }
- if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
- vib.getUid(), -1 /*owningUid*/, true /*exported*/)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
- + " tried to play externally controlled vibration"
- + " without VIBRATE permission, ignoring.");
- return IExternalVibratorService.SCALE_MUTE;
- }
-
- int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return IExternalVibratorService.SCALE_MUTE;
- }
-
- VibrationThread cancelingVibration = null;
- int scale;
- synchronized (mLock) {
- if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
- // We are already playing this external vibration, so we can return the same
- // scale calculated in the previous call to this method.
- return mCurrentExternalVibration.scale;
- }
- if (mCurrentExternalVibration == null) {
- // If we're not under external control right now, then cancel any normal
- // vibration that may be playing and ready the vibrator for external control.
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- cancelingVibration = mThread;
- } else {
- endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
- }
- // At this point we either have an externally controlled vibration playing, or
- // no vibration playing. Since the interface defines that only one externally
- // controlled vibration can play at a time, by returning something other than
- // SCALE_MUTE from this function we can be assured that if we are currently
- // playing vibration, it will be muted in favor of the new vibration.
- //
- // Note that this doesn't support multiple concurrent external controls, as we
- // would need to mute the old one still if it came from a different controller.
- mCurrentExternalVibration = new ExternalVibrationHolder(vib);
- mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
- vib.linkToDeath(mCurrentExternalDeathRecipient);
- mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
- vib.getVibrationAttributes().getUsage());
- scale = mCurrentExternalVibration.scale;
- }
- if (cancelingVibration != null) {
- try {
- cancelingVibration.join();
- } catch (InterruptedException e) {
- Slog.w("Interrupted while waiting current vibration to be cancelled before "
- + "starting external vibration", e);
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "Vibrator going under external control.");
- }
- mVibratorController.setExternalControl(true);
- if (DEBUG) {
- Slog.e(TAG, "Playing external vibration: " + vib);
- }
- return scale;
- }
-
- @Override
- public void onExternalVibrationStop(ExternalVibration vib) {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
- if (DEBUG) {
- Slog.e(TAG, "Stopping external vibration" + vib);
- }
- doCancelExternalVibrateLocked(Vibration.Status.FINISHED);
- }
- }
- }
-
- private void doCancelExternalVibrateLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
- try {
- if (mCurrentExternalVibration == null) {
- return;
- }
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.unlinkToDeath(
- mCurrentExternalDeathRecipient);
- mCurrentExternalDeathRecipient = null;
- mCurrentExternalVibration = null;
- mVibratorController.setExternalControl(false);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
- public void binderDied() {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "External vibration finished because binder died");
- }
- doCancelExternalVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
- }
- }
-
- private final class VibratorShellCommand extends ShellCommand {
-
- private final IBinder mToken;
-
- private final class CommonOptions {
- public boolean force = false;
- public void check(String opt) {
- switch (opt) {
- case "-f":
- force = true;
- break;
- }
- }
- }
-
- private VibratorShellCommand(IBinder token) {
- mToken = token;
- }
-
- @Override
- public int onCommand(String cmd) {
- if ("vibrate".equals(cmd)) {
- return runVibrate();
- } else if ("waveform".equals(cmd)) {
- return runWaveform();
- } else if ("prebaked".equals(cmd)) {
- return runPrebaked();
- } else if ("capabilities".equals(cmd)) {
- return runCapabilities();
- } else if ("cancel".equals(cmd)) {
- cancelVibrate(mToken);
- return 0;
- }
- return handleDefaultCommands(cmd);
- }
-
- private boolean checkDoNotDisturb(CommonOptions opts) {
- if (mVibrationSettings.isInZenMode() && !opts.force) {
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.print("Ignoring because device is on DND mode ");
- return true;
- }
- }
- return false;
- }
-
- private int runVibrate() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
- try {
- CommonOptions commonOptions = new CommonOptions();
-
- String opt;
- while ((opt = getNextOption()) != null) {
- commonOptions.check(opt);
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- final long duration = Long.parseLong(getNextArgRequired());
- String description = getNextArg();
- if (description == null) {
- description = "Shell command";
- }
-
- VibrationEffect effect =
- VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runWaveform() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
- try {
- String description = "Shell command";
- int repeat = -1;
- ArrayList<Integer> amplitudesList = null;
- CommonOptions commonOptions = new CommonOptions();
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-d":
- description = getNextArgRequired();
- break;
- case "-r":
- repeat = Integer.parseInt(getNextArgRequired());
- break;
- case "-a":
- if (amplitudesList == null) {
- amplitudesList = new ArrayList<Integer>();
- }
- break;
- default:
- commonOptions.check(opt);
- break;
- }
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- ArrayList<Long> timingsList = new ArrayList<Long>();
-
- String arg;
- while ((arg = getNextArg()) != null) {
- if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
- amplitudesList.add(Integer.parseInt(arg));
- } else {
- timingsList.add(Long.parseLong(arg));
- }
- }
-
- VibrationEffect effect;
- long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
- if (amplitudesList == null) {
- effect = VibrationEffect.createWaveform(timings, repeat);
- } else {
- int[] amplitudes =
- amplitudesList.stream().mapToInt(Integer::intValue).toArray();
- effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
- }
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runPrebaked() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
- try {
- CommonOptions commonOptions = new CommonOptions();
- boolean shouldFallback = false;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if ("-b".equals(opt)) {
- shouldFallback = true;
- } else {
- commonOptions.check(opt);
- }
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- final int id = Integer.parseInt(getNextArgRequired());
-
- String description = getNextArg();
- if (description == null) {
- description = "Shell command";
- }
-
- VibrationEffect effect = VibrationEffect.get(id, shouldFallback);
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runCapabilities() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities");
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.println("Vibrator capabilities:");
- if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
- pw.println(" Always on effects");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
- pw.println(" Compose effects");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
- pw.println(" Amplitude control");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
- pw.println(" External control");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
- pw.println(" External amplitude control");
- }
- pw.println("");
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
- final int flags = commonOptions.force
- ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
- : 0;
- return new VibrationAttributes.Builder()
- .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
- // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
- .setUsage(VibrationAttributes.USAGE_TOUCH)
- .build();
- }
-
- @Override
- public void onHelp() {
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.println("Vibrator commands:");
- pw.println(" help");
- pw.println(" Prints this help text.");
- pw.println("");
- pw.println(" vibrate duration [description]");
- pw.println(" Vibrates for duration milliseconds; ignored when device is on ");
- pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
- pw.println(" waveform [-d description] [-r index] [-a] duration [amplitude] ...");
- pw.println(" Vibrates for durations and amplitudes in list; ignored when ");
- pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength ");
- pw.println(" user setting will be used to scale amplitude.");
- pw.println(" If -r is provided, the waveform loops back to the specified");
- pw.println(" index (e.g. 0 loops from the beginning)");
- pw.println(" If -a is provided, the command accepts duration-amplitude pairs;");
- pw.println(" otherwise, it accepts durations only and alternates off/on");
- pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255.");
- pw.println(" prebaked [-b] effect-id [description]");
- pw.println(" Vibrates with prebaked effect; ignored when device is on DND ");
- pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
- pw.println(" If -b is provided, the prebaked fallback effect will be played if");
- pw.println(" the device doesn't support the given effect-id.");
- pw.println(" capabilities");
- pw.println(" Prints capabilities of this device.");
- pw.println(" cancel");
- pw.println(" Cancels any active vibration");
- pw.println("Common Options:");
- pw.println(" -f - Force. Ignore Do Not Disturb setting.");
- pw.println("");
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
new file mode 100644
index 0000000..5d89bf1
--- /dev/null
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -0,0 +1,918 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.NETWORK_STACK;
+
+import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.IVpnManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkStack;
+import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
+import android.net.VpnManager;
+import android.net.VpnService;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.LockdownVpnTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs.
+ * @hide
+ */
+public class VpnManagerService extends IVpnManager.Stub {
+ private static final String TAG = VpnManagerService.class.getSimpleName();
+
+ @VisibleForTesting
+ protected final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+
+ private final Context mContext;
+ private final Context mUserAllContext;
+
+ private final Dependencies mDeps;
+
+ private final ConnectivityManager mCm;
+ private final KeyStore mKeyStore;
+ private final INetworkManagementService mNMS;
+ private final INetd mNetd;
+ private final UserManager mUserManager;
+
+ @VisibleForTesting
+ @GuardedBy("mVpns")
+ protected final SparseArray<Vpn> mVpns = new SparseArray<>();
+
+ // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+ // a direct call to LockdownVpnTracker.isEnabled().
+ @GuardedBy("mVpns")
+ private boolean mLockdownEnabled;
+ @GuardedBy("mVpns")
+ private LockdownVpnTracker mLockdownTracker;
+
+ /**
+ * Dependencies of VpnManager, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Returns the calling UID of an IPC. */
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ /** Creates a HandlerThread to be used by this class. */
+ public HandlerThread makeHandlerThread() {
+ return new HandlerThread("VpnManagerService");
+ }
+
+ /** Returns the KeyStore instance to be used by this class. */
+ public KeyStore getKeyStore() {
+ return KeyStore.getInstance();
+ }
+
+ public INetd getNetd() {
+ return NetdService.getInstance();
+ }
+
+ public INetworkManagementService getINetworkManagementService() {
+ return INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ }
+ }
+
+ public VpnManagerService(Context context, Dependencies deps) {
+ mContext = context;
+ mDeps = deps;
+ mHandlerThread = mDeps.makeHandlerThread();
+ mHandlerThread.start();
+ mHandler = mHandlerThread.getThreadHandler();
+ mKeyStore = mDeps.getKeyStore();
+ mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mNMS = mDeps.getINetworkManagementService();
+ mNetd = mDeps.getNetd();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ registerReceivers();
+ log("VpnManagerService starting up");
+ }
+
+ /** Creates a new VpnManagerService */
+ public static VpnManagerService create(Context context) {
+ return new VpnManagerService(context, new Dependencies());
+ }
+
+ /** Informs the service that the system is ready. */
+ public void systemReady() {
+ // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
+ // for user to unlock device too.
+ updateLockdownVpn();
+ }
+
+ @Override
+ /** Dumps service state. */
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+ @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println("VPNs:");
+ pw.increaseIndent();
+ synchronized (mVpns) {
+ for (int i = 0; i < mVpns.size(); i++) {
+ pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ /**
+ * Prepare for a VPN application.
+ * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+ * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+ *
+ * @param oldPackage Package name of the application which currently controls VPN, which will
+ * be replaced. If there is no such application, this should should either be
+ * {@code null} or {@link VpnConfig.LEGACY_VPN}.
+ * @param newPackage Package name of the application which should gain control of VPN, or
+ * {@code null} to disable.
+ * @param userId User for whom to prepare the new VPN.
+ *
+ * @hide
+ */
+ @Override
+ public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+ int userId) {
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Set whether the VPN package has the ability to launch VPNs without user intervention. This
+ * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
+ * class. If the caller is not {@code userId}, {@link
+ * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+ *
+ * @param packageName The package for which authorization state should change.
+ * @param userId User for whom {@code packageName} is installed.
+ * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
+ * permissions should be granted. When unauthorizing an app, {@link
+ * VpnManager.TYPE_VPN_NONE} should be used.
+ * @hide
+ */
+ @Override
+ public void setVpnPackageAuthorization(
+ String packageName, int userId, @VpnManager.VpnType int vpnType) {
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ vpn.setPackageAuthorization(packageName, vpnType);
+ }
+ }
+ }
+
+ /**
+ * Configure a TUN interface and return its file descriptor. Parameters
+ * are encoded and opaque to this class. This method is used by VpnBuilder
+ * and not available in VpnManager. Permissions are checked in
+ * Vpn class.
+ * @hide
+ */
+ @Override
+ public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ return mVpns.get(user).establish(config);
+ }
+ }
+
+ @Override
+ public boolean addVpnAddress(String address, int prefixLength) {
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ return mVpns.get(user).addAddress(address, prefixLength);
+ }
+ }
+
+ @Override
+ public boolean removeVpnAddress(String address, int prefixLength) {
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ return mVpns.get(user).removeAddress(address, prefixLength);
+ }
+ }
+
+ @Override
+ public boolean setUnderlyingNetworksForVpn(Network[] networks) {
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ final boolean success;
+ synchronized (mVpns) {
+ success = mVpns.get(user).setUnderlyingNetworks(networks);
+ }
+ return success;
+ }
+
+ /**
+ * Stores the given VPN profile based on the provisioning package name.
+ *
+ * <p>If there is already a VPN profile stored for the provisioning package, this call will
+ * overwrite the profile.
+ *
+ * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+ * exclusively by the Settings app, and passed into the platform at startup time.
+ *
+ * @return {@code true} if user consent has already been granted, {@code false} otherwise.
+ * @hide
+ */
+ @Override
+ public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
+ }
+ }
+
+ /**
+ * Deletes the stored VPN profile for the provisioning package
+ *
+ * <p>If there are no profiles for the given package, this method will silently succeed.
+ *
+ * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+ * exclusively by the Settings app, and passed into the platform at startup time.
+ *
+ * @hide
+ */
+ @Override
+ public void deleteVpnProfile(@NonNull String packageName) {
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
+ }
+ }
+
+ /**
+ * Starts the VPN based on the stored profile for the given package
+ *
+ * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+ * exclusively by the Settings app, and passed into the platform at startup time.
+ *
+ * @throws IllegalArgumentException if no profile was found for the given package name.
+ * @hide
+ */
+ @Override
+ public void startVpnProfile(@NonNull String packageName) {
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ mVpns.get(user).startVpnProfile(packageName, mKeyStore);
+ }
+ }
+
+ /**
+ * Stops the Platform VPN if the provided package is running one.
+ *
+ * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+ * exclusively by the Settings app, and passed into the platform at startup time.
+ *
+ * @hide
+ */
+ @Override
+ public void stopVpnProfile(@NonNull String packageName) {
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
+ synchronized (mVpns) {
+ mVpns.get(user).stopVpnProfile(packageName);
+ }
+ }
+
+ /**
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
+ */
+ @Override
+ public void startLegacyVpn(VpnProfile profile) {
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ final LinkProperties egress = mCm.getActiveLinkProperties();
+ if (egress == null) {
+ throw new IllegalStateException("Missing active network connection");
+ }
+ synchronized (mVpns) {
+ throwIfLockdownEnabled();
+ mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
+ }
+ }
+
+ /**
+ * Return the information of the ongoing legacy VPN. This method is used
+ * by VpnSettings and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ */
+ @Override
+ public LegacyVpnInfo getLegacyVpnInfo(int userId) {
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ return mVpns.get(userId).getLegacyVpnInfo();
+ }
+ }
+
+ /**
+ * Returns the information of the ongoing VPN for {@code userId}. This method is used by
+ * VpnDialogs and not available in ConnectivityManager.
+ * Permissions are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public VpnConfig getVpnConfig(int userId) {
+ enforceCrossUserPermission(userId);
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.getVpnConfig();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private boolean isLockdownVpnEnabled() {
+ return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
+ }
+
+ @Override
+ public boolean updateLockdownVpn() {
+ // Allow the system UID for the system server and for Settings.
+ // Also, for unit tests, allow the process that ConnectivityService is running in.
+ if (mDeps.getCallingUid() != Process.SYSTEM_UID
+ && Binder.getCallingPid() != Process.myPid()) {
+ logw("Lockdown VPN only available to system process or AID_SYSTEM");
+ return false;
+ }
+
+ synchronized (mVpns) {
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = isLockdownVpnEnabled();
+ if (!mLockdownEnabled) {
+ setLockdownTracker(null);
+ return true;
+ }
+
+ byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+ if (profileTag == null) {
+ loge("Lockdown VPN configured but cannot be read from keystore");
+ return false;
+ }
+ String profileName = new String(profileTag);
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ if (profile == null) {
+ loge("Lockdown VPN configured invalid profile " + profileName);
+ setLockdownTracker(null);
+ return true;
+ }
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
+ Vpn vpn = mVpns.get(user);
+ if (vpn == null) {
+ logw("VPN for user " + user + " not ready yet. Skipping lockdown");
+ return false;
+ }
+ setLockdownTracker(
+ new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile));
+ }
+
+ return true;
+ }
+
+ /**
+ * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+ * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+ */
+ @GuardedBy("mVpns")
+ private void setLockdownTracker(LockdownVpnTracker tracker) {
+ // Shutdown any existing tracker
+ final LockdownVpnTracker existing = mLockdownTracker;
+ // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
+ // necessary onBlockedStatusChanged callbacks.
+ mLockdownTracker = null;
+ if (existing != null) {
+ existing.shutdown();
+ }
+
+ if (tracker != null) {
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
+ }
+ }
+
+ /**
+ * Throws if there is any currently running, always-on Legacy VPN.
+ *
+ * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
+ * running across the entire system. Tracking for app-based VPNs is done on a per-user,
+ * per-package basis in Vpn.java
+ */
+ @GuardedBy("mVpns")
+ private void throwIfLockdownEnabled() {
+ if (mLockdownEnabled) {
+ throw new IllegalStateException("Unavailable in lockdown mode");
+ }
+ }
+
+ /**
+ * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
+ * some setup and then call {@code establish()} to connect.
+ *
+ * @return {@code true} if the service was started, the service was already connected, or there
+ * was no always-on VPN to start. {@code false} otherwise.
+ */
+ private boolean startAlwaysOnVpn(int userId) {
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ // Shouldn't happen as all code paths that point here should have checked the Vpn
+ // exists already.
+ Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
+ return false;
+ }
+
+ return vpn.startAlwaysOnVpn(mKeyStore);
+ }
+ }
+
+ @Override
+ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+ enforceSettingsPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ logw("User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
+ }
+ }
+
+ @Override
+ public boolean setAlwaysOnVpnPackage(
+ int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+ if (isLockdownVpnEnabled()) {
+ return false;
+ }
+
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ logw("User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
+ return false;
+ }
+ if (!startAlwaysOnVpn(userId)) {
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String getAlwaysOnVpnPackage(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ logw("User " + userId + " has no Vpn configuration");
+ return null;
+ }
+ return vpn.getAlwaysOnPackage();
+ }
+ }
+
+ @Override
+ public boolean isVpnLockdownEnabled(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ logw("User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.getLockdown();
+ }
+ }
+
+ @Override
+ public List<String> getVpnLockdownAllowlist(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ logw("User " + userId + " has no Vpn configuration");
+ return null;
+ }
+ return vpn.getLockdownAllowlist();
+ }
+ }
+
+ @GuardedBy("mVpns")
+ private Vpn getVpnIfOwner() {
+ return getVpnIfOwner(mDeps.getCallingUid());
+ }
+
+ // TODO: stop calling into Vpn.java and get this information from data in this class.
+ @GuardedBy("mVpns")
+ private Vpn getVpnIfOwner(int uid) {
+ final int user = UserHandle.getUserId(uid);
+
+ final Vpn vpn = mVpns.get(user);
+ if (vpn == null) {
+ return null;
+ } else {
+ final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
+ return (info == null || info.ownerUid != uid) ? null : vpn;
+ }
+ }
+
+ private void registerReceivers() {
+ // Set up the listener for user state for creating user VPNs.
+ // Should run on mHandler to avoid any races.
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_STARTED);
+ intentFilter.addAction(Intent.ACTION_USER_STOPPED);
+ intentFilter.addAction(Intent.ACTION_USER_ADDED);
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+
+ mUserAllContext.registerReceiver(
+ mIntentReceiver,
+ intentFilter,
+ null /* broadcastPermission */,
+ mHandler);
+ mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
+ mUserPresentReceiver,
+ new IntentFilter(Intent.ACTION_USER_PRESENT),
+ null /* broadcastPermission */,
+ mHandler /* scheduler */);
+
+ // Listen to package add and removal events for all users.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mUserAllContext.registerReceiver(
+ mIntentReceiver,
+ intentFilter,
+ null /* broadcastPermission */,
+ mHandler);
+
+ // Listen to lockdown VPN reset.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
+ mUserAllContext.registerReceiver(
+ mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+ }
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ensureRunningOnHandlerThread();
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final Uri packageData = intent.getData();
+ final String packageName =
+ packageData != null ? packageData.getSchemeSpecificPart() : null;
+
+ if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
+ onVpnLockdownReset();
+ }
+
+ // UserId should be filled for below intents, check the existence.
+ if (userId == UserHandle.USER_NULL) return;
+
+ if (Intent.ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(userId);
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ onUserStopped(userId);
+ } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+ onUserAdded(userId);
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ onUserRemoved(userId);
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ onUserUnlocked(userId);
+ } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ onPackageReplaced(packageName, uid);
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ final boolean isReplacing = intent.getBooleanExtra(
+ Intent.EXTRA_REPLACING, false);
+ onPackageRemoved(packageName, uid, isReplacing);
+ } else {
+ Log.wtf(TAG, "received unexpected intent: " + action);
+ }
+ }
+ };
+
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ensureRunningOnHandlerThread();
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ updateLockdownVpn();
+ // Use the same context that registered receiver before to unregister it. Because use
+ // different context to unregister receiver will cause exception.
+ context.unregisterReceiver(this);
+ }
+ };
+
+ private void onUserStarted(int userId) {
+ synchronized (mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn != null) {
+ loge("Starting user already has a VPN");
+ return;
+ }
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
+ mVpns.put(userId, userVpn);
+ if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+ updateLockdownVpn();
+ }
+ }
+ }
+
+ private void onUserStopped(int userId) {
+ synchronized (mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn == null) {
+ loge("Stopped user has no VPN");
+ return;
+ }
+ userVpn.onUserStopped();
+ mVpns.delete(userId);
+ }
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getAlwaysOn();
+ }
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getLockdown();
+ }
+ }
+
+
+ private void onUserAdded(int userId) {
+ synchronized (mVpns) {
+ final int vpnsSize = mVpns.size();
+ for (int i = 0; i < vpnsSize; i++) {
+ Vpn vpn = mVpns.valueAt(i);
+ vpn.onUserAdded(userId);
+ }
+ }
+ }
+
+ private void onUserRemoved(int userId) {
+ synchronized (mVpns) {
+ final int vpnsSize = mVpns.size();
+ for (int i = 0; i < vpnsSize; i++) {
+ Vpn vpn = mVpns.valueAt(i);
+ vpn.onUserRemoved(userId);
+ }
+ }
+ }
+
+ private void onPackageReplaced(String packageName, int uid) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+ return;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ return;
+ }
+ // Legacy always-on VPN won't be affected since the package name is not set.
+ if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+ log("Restarting always-on VPN package " + packageName + " for user "
+ + userId);
+ vpn.startAlwaysOnVpn(mKeyStore);
+ }
+ }
+ }
+
+ private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ return;
+ }
+ // Legacy always-on VPN won't be affected since the package name is not set.
+ if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+ log("Removing always-on VPN package " + packageName + " for user "
+ + userId);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+ }
+ }
+ }
+
+ private void onUserUnlocked(int userId) {
+ synchronized (mVpns) {
+ // User present may be sent because of an unlock, which might mean an unlocked keystore.
+ if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+ updateLockdownVpn();
+ } else {
+ startAlwaysOnVpn(userId);
+ }
+ }
+ }
+
+ private void onVpnLockdownReset() {
+ synchronized (mVpns) {
+ if (mLockdownTracker != null) mLockdownTracker.reset();
+ }
+ }
+
+
+ @Override
+ public void factoryReset() {
+ enforceSettingsPermission();
+
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)
+ || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
+ return;
+ }
+
+ // Remove always-on package
+ final int userId = UserHandle.getCallingUserId();
+ synchronized (mVpns) {
+ final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
+ if (alwaysOnPackage != null) {
+ setAlwaysOnVpnPackage(userId, null, false, null);
+ setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
+ }
+
+ // Turn Always-on VPN off
+ if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+ mLockdownEnabled = false;
+ setLockdownTracker(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // Turn VPN off
+ VpnConfig vpnConfig = getVpnConfig(userId);
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+ } else {
+ // Prevent this app (packagename = vpnConfig.user) from initiating
+ // VPN connections in the future without user intervention.
+ setVpnPackageAuthorization(
+ vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
+
+ prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+ }
+ }
+ }
+ }
+
+ private void ensureRunningOnHandlerThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on VpnManagerService thread: "
+ + Thread.currentThread().getName());
+ }
+ }
+
+ private void enforceControlAlwaysOnVpnPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
+ "VpnManagerService");
+ }
+
+ /**
+ * Require that the caller is either in the same user or has appropriate permission to interact
+ * across users.
+ *
+ * @param userId Target user for whatever operation the current IPC is supposed to perform.
+ */
+ private void enforceCrossUserPermission(int userId) {
+ if (userId == UserHandle.getCallingUserId()) {
+ // Not a cross-user call.
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "VpnManagerService");
+ }
+
+ private void enforceSettingsPermission() {
+ enforceAnyPermissionOf(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
+ private static void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void logw(String s) {
+ Log.w(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/services/core/java/com/android/server/accounts/OWNERS
+++ b/services/core/java/com/android/server/accounts/OWNERS
@@ -3,7 +3,6 @@
sandrakwan@google.com
hackbod@google.com
svetoslavganov@google.com
-moltmann@google.com
fkupolov@google.com
yamasani@google.com
omakoto@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f0f29a9..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;
@@ -1490,7 +1491,9 @@
private static final int INDEX_TOTAL_SWAP_PSS = 10;
private static final int INDEX_TOTAL_RSS = 11;
private static final int INDEX_TOTAL_NATIVE_PSS = 12;
- private static final int INDEX_LAST = 13;
+ private static final int INDEX_TOTAL_MEMTRACK_GRAPHICS = 13;
+ private static final int INDEX_TOTAL_MEMTRACK_GL = 14;
+ private static final int INDEX_LAST = 15;
final class UiHandler extends Handler {
public UiHandler() {
@@ -5624,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.
@@ -10316,6 +10336,7 @@
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+ long[] memtrackTmp = new long[4];
long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
@@ -10349,6 +10370,8 @@
final int reportType;
final long startTime;
final long endTime;
+ long memtrackGraphics = 0;
+ long memtrackGl = 0;
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
startTime = SystemClock.currentThreadTimeMillis();
@@ -10360,7 +10383,7 @@
} else {
reportType = ProcessStats.ADD_PSS_EXTERNAL;
startTime = SystemClock.currentThreadTimeMillis();
- long pss = Debug.getPss(pid, tmpLong, null);
+ long pss = Debug.getPss(pid, tmpLong, memtrackTmp);
if (pss == 0) {
continue;
}
@@ -10368,6 +10391,8 @@
endTime = SystemClock.currentThreadTimeMillis();
mi.dalvikPrivateDirty = (int) tmpLong[0];
mi.dalvikRss = (int) tmpLong[2];
+ memtrackGraphics = memtrackTmp[1];
+ memtrackGl = memtrackTmp[2];
}
if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
@@ -10431,6 +10456,8 @@
ss[INDEX_TOTAL_PSS] += myTotalPss;
ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
ss[INDEX_TOTAL_RSS] += myTotalRss;
+ ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+ ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
(hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
myTotalSwapPss, myTotalRss, pid, hasActivities);
@@ -10494,6 +10521,8 @@
final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1];
mAppProfiler.forAllCpuStats((st) -> {
if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+ long memtrackGraphics = 0;
+ long memtrackGl = 0;
if (memInfos[0] == null) {
memInfos[0] = new Debug.MemoryInfo();
}
@@ -10503,13 +10532,15 @@
return;
}
} else {
- long pss = Debug.getPss(st.pid, tmpLong, null);
+ long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp);
if (pss == 0) {
return;
}
info.nativePss = (int) pss;
info.nativePrivateDirty = (int) tmpLong[0];
info.nativeRss = (int) tmpLong[2];
+ memtrackGraphics = memtrackTmp[1];
+ memtrackGl = memtrackTmp[2];
}
final long myTotalPss = info.getTotalPss();
@@ -10519,6 +10550,8 @@
ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
ss[INDEX_TOTAL_RSS] += myTotalRss;
ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss;
+ ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+ ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
@@ -10726,8 +10759,21 @@
pw.print(" mapped + ");
pw.print(stringifyKBSize(dmabufUnmapped));
pw.println(" unmapped)");
- kernelUsed += totalExportedDmabuf;
+ // Account unmapped dmabufs as part of kernel memory allocations
+ kernelUsed += dmabufUnmapped;
+ // Replace memtrack HAL reported Graphics category with mapped dmabufs
+ 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: ");
@@ -10736,13 +10782,27 @@
}
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
- pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage));
+ final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+ if (gpuDmaBufUsage >= 0) {
+ final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+ pw.print(" GPU: ");
+ pw.print(stringifyKBSize(gpuUsage));
+ pw.print(" (");
+ pw.print(stringifyKBSize(gpuDmaBufUsage));
+ pw.print(" dmabuf + ");
+ pw.print(stringifyKBSize(gpuPrivateUsage));
+ pw.println(" private)");
+ // Replace memtrack HAL reported GL category with private GPU allocations and
+ // account it as part of kernel memory allocations
+ ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GL];
+ kernelUsed += gpuPrivateUsage;
+ } else {
+ pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage));
+ }
}
- /*
- * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
- * memInfo.getCachedSizeKb().
- */
+ // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+ // memInfo.getCachedSizeKb().
final long lostRAM = memInfo.getTotalSizeKb()
- (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3ff5872..c8630fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1322,7 +1322,7 @@
infoMap.put(mi.pid, mi);
}
updateCpuStatsNow();
- long[] memtrackTmp = new long[1];
+ long[] memtrackTmp = new long[4];
long[] swaptrackTmp = new long[2];
// Get a list of Stats that have vsize > 0
final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0);
@@ -1345,6 +1345,8 @@
long totalPss = 0;
long totalSwapPss = 0;
long totalMemtrack = 0;
+ long totalMemtrackGraphics = 0;
+ long totalMemtrackGl = 0;
for (int i = 0, size = memInfos.size(); i < size; i++) {
ProcessMemInfo mi = memInfos.get(i);
if (mi.pss == 0) {
@@ -1355,6 +1357,8 @@
totalPss += mi.pss;
totalSwapPss += mi.swapPss;
totalMemtrack += mi.memtrack;
+ totalMemtrackGraphics += memtrackTmp[1];
+ totalMemtrackGl += memtrackTmp[2];
}
Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
@Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
@@ -1521,10 +1525,24 @@
} else {
final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
if (totalExportedDmabuf >= 0) {
+ final long dmabufMapped = Debug.getDmabufMappedSizeKb();
+ final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
memInfoBuilder.append("DMA-BUF: ");
memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
memInfoBuilder.append("\n");
- kernelUsed += totalExportedDmabuf;
+ // Account unmapped dmabufs as part of kernel memory allocations
+ kernelUsed += dmabufUnmapped;
+ // Replace memtrack HAL reported Graphics category with mapped dmabufs
+ 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();
@@ -1537,19 +1555,34 @@
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
- memInfoBuilder.append(" GPU: ");
- memInfoBuilder.append(stringifyKBSize(gpuUsage));
- memInfoBuilder.append("\n");
+ final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+ if (gpuDmaBufUsage >= 0) {
+ final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+ memInfoBuilder.append(" GPU: ");
+ memInfoBuilder.append(stringifyKBSize(gpuUsage));
+ memInfoBuilder.append(" (");
+ memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage));
+ memInfoBuilder.append(" dmabuf + ");
+ memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage));
+ memInfoBuilder.append(" private)\n");
+ // Replace memtrack HAL reported GL category with private GPU allocations and
+ // account it as part of kernel memory allocations
+ totalPss -= totalMemtrackGl;
+ kernelUsed += gpuPrivateUsage;
+ } else {
+ memInfoBuilder.append(" GPU: ");
+ memInfoBuilder.append(stringifyKBSize(gpuUsage));
+ memInfoBuilder.append("\n");
+ }
+
}
memInfoBuilder.append(" Used RAM: ");
memInfoBuilder.append(stringifyKBSize(
totalPss - cachedPss + kernelUsed));
memInfoBuilder.append("\n");
- /*
- * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
- * memInfo.getCachedSizeKb().
- */
+ // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+ // memInfo.getCachedSizeKb().
memInfoBuilder.append(" Lost RAM: ");
memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
- (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..52bb55f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
*/
package com.android.server.am;
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
import android.annotation.Nullable;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
import android.telephony.TelephonyManager;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
private WifiActivityEnergyInfo mLastWifiInfo =
new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
- /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+ /**
+ * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+ * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+ */
+ // TODO(b/180029015): Hook this up (it isn't used yet)
@GuardedBy("mWorkerLock")
- private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
- /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
- @GuardedBy("mWorkerLock")
- private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+ private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
/** Snapshot of measured energies, or null if no measured energies are supported. */
@GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
mWifiManager = wm;
mTelephony = tm;
mPowerStatsInternal = psi;
+
+ boolean[] supportedStdBuckets = null;
+ int numCustomBuckets = 0;
if (mPowerStatsInternal != null) {
- populateEnergyConsumerSubsystemMapsLocked();
- final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
- mMeasuredEnergySnapshot = initialMeasuredEnergies == null
- ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
- final boolean[] supportedStdBuckets
- = getSupportedEnergyBuckets(initialMeasuredEnergies);
- final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
- synchronized (mStats) {
- mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+ final SparseArray<EnergyConsumer> idToConsumer
+ = populateEnergyConsumerSubsystemMapsLocked();
+ if (idToConsumer != null) {
+ mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+ final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+ // According to spec, initialEcrs will include 0s for consumers that haven't
+ // used any energy yet, as long as they are supported; however, attributed uid
+ // energies will be absent if their energy is 0.
+ mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+ numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+ supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
}
}
+ synchronized (mStats) {
+ mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+ }
}
}
@@ -568,7 +573,9 @@
} catch (ExecutionException e) {
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
}
- final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+ final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+ mMeasuredEnergySnapshot == null ? null :
mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
final long elapsedRealtimeUs = elapsedRealtime * 1000;
final long uptimeUs = uptime * 1000;
+ // Now that we have finally received all the data, we can tell mStats about it.
synchronized (mStats) {
mStats.addHistoryEventLocked(
elapsedRealtime,
@@ -601,10 +609,21 @@
}
// Inform mStats about each applicable measured energy.
- if (energyDeltas != null) {
- final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
- // Always pass in what BatteryExternalStatsWorker thinks screenState is.
- mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+ if (measuredEnergyDeltas != null) {
+ final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+ if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+ // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+ mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+ }
+ }
+ // Inform mStats about each applicable custom energy bucket.
+ if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+ // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+ for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+ long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+ SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+ mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+ }
}
if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
if (wifiInfo != null) {
if (wifiInfo.isValid()) {
- // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+ // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+ // .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
mStats.updateWifiState(extractDeltaLocked(wifiInfo)
/*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
} else {
@@ -740,21 +760,23 @@
}
/**
- * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+ * Map the {@link EnergyConsumerType}s in the given energyArray to
* their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
* Does not include custom energy buckets (which are always, by definition, supported).
*
* @return array with true for index i if standard energy bucket i is supported.
*/
- private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
- if (energyArray == null) {
+ private static @Nullable boolean[] getSupportedEnergyBuckets(
+ SparseArray<EnergyConsumer> idToConsumer) {
+ if (idToConsumer == null) {
return null;
}
final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
- final int size = energyArray.size();
- for (int energyIdx = 0; energyIdx < size; energyIdx++) {
- switch (energyArray.getSubsystem(energyIdx)) {
- case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+ final int size = idToConsumer.size();
+ for (int idx = 0; idx < size; idx++) {
+ final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+ switch (consumer.type) {
+ case EnergyConsumerType.DISPLAY:
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
return buckets;
}
- /**
- * Get a {@link MeasuredEnergyArray} with the latest
- * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
- *
- * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
- * EnergyConsumerResult}[]
- */
+ /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
@GuardedBy("mWorkerLock")
- @VisibleForTesting
- public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
- final EnergyConsumerResult[] results;
+ private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
try {
- results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+ return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
return null;
}
- if (results == null) return null;
- final int size = results.length;
- final int[] subsystems = new int[size];
- final long[] energyUJ = new long[size];
-
- int count = 0;
- for (int i = 0; i < size; i++) {
- final EnergyConsumerResult consumer = results[i];
- final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
- MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
- if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
- subsystems[count] = subsystem;
- energyUJ[count] = consumer.energyUWs;
- count++;
- }
- final int arraySize = count;
- return new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- if (index >= size()) {
- throw new IllegalArgumentException(
- "Out of bounds subsystem index! index : " + index + ", size : "
- + size());
- }
- return subsystems[index];
- }
-
- @Override
- public long getEnergy(int index) {
- if (index >= size()) {
- throw new IllegalArgumentException(
- "Out of bounds subsystem index! index : " + index + ", size : "
- + size());
- }
- return energyUJ[index];
- }
-
- @Override
- public int size() {
- return arraySize;
- }
- };
}
- /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+ /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
@GuardedBy("mWorkerLock")
- private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+ private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+ {
if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
if (flags == UPDATE_ALL) {
@@ -836,72 +809,62 @@
return getEnergyConsumptionData();
}
- final List<Integer> energySubsystems = new ArrayList<>();
+ final List<Integer> energyConsumerIds = new ArrayList<>();
if ((flags & UPDATE_DISPLAY) != 0) {
- addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+ addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
}
// TODO: Wifi, Bluetooth, etc., go here
- if (energySubsystems.isEmpty()) {
+
+ if (energyConsumerIds.isEmpty()) {
return null;
}
- // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
+ // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
return getEnergyConsumptionData();
}
@GuardedBy("mWorkerLock")
- private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
- @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
- if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
- energyConsumerIds.add(consumerId);
- }
+ private void addEnergyConsumerIdLocked(
+ List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+ final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
+ energyConsumerIds.add(consumerId);
}
+ /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
@GuardedBy("mWorkerLock")
- private void populateEnergyConsumerSubsystemMapsLocked() {
+ private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
if (mPowerStatsInternal == null) {
- // PowerStatsInternal unavailable, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
+ return null;
}
final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
- if (energyConsumers == null) {
- // EnergyConsumer data unavailable, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
+ if (energyConsumers == null || energyConsumers.length == 0) {
+ return null;
}
- final int length = energyConsumers.length;
- if (length == 0) {
- // EnergyConsumer array empty, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
- } else {
- mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
- mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
- }
+ // TODO(b/180029015): Initialize typeToIds
+ // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+ // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+ // Maps id -> EnergyConsumer (1:1 map)
+ final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
// Add all expected EnergyConsumers to the maps
- for (int i = 0; i < length; i++) {
- final EnergyConsumer consumer = energyConsumers[i];
- switch (consumer.type) {
- case EnergyConsumerType.DISPLAY:
- if (consumer.ordinal == 0) {
- mEnergyConsumerToSubsystemMap.put(consumer.id,
- MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
- mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
- consumer.id);
- } else {
- Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
- + ") for EnergyConsumerType.DISPLAY");
- }
- break;
- default:
- Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+ for (final EnergyConsumer consumer : energyConsumers) {
+ // Check for inappropriate ordinals
+ if (consumer.ordinal != 0) {
+ switch (consumer.type) {
+ case EnergyConsumerType.OTHER:
+ case EnergyConsumerType.CPU_CLUSTER:
+ break;
+ default:
+ Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+ + consumer.ordinal + " for type " + consumer.type);
+ continue; // Ignore this consumer
+ }
}
-
+ idToConsumer.put(consumer.id, consumer);
+ // TODO(b/180029015): Also populate typeToIds map
}
+ // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+ return idToConsumer;
}
}
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
import java.io.PrintWriter;
-import java.util.Arrays;
/**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
*/
@VisibleForTesting
public class MeasuredEnergySnapshot {
private static final String TAG = "MeasuredEnergySnapshot";
- private static final long UNAVAILABLE = -1;
+ public static final long UNAVAILABLE = -1L;
+
+ /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+ private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+ /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+ private final int mNumOtherOrdinals;
/**
- * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+ * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+ * each {@link EnergyConsumer} was updated.
*
- * Note that the snapshots for different subsystems may have been taken at different times.
+ * Note that the snapshots for different ids may have been taken at different times.
+ * Note that energies for all existing ids are stored here, including each ordinal of type
+ * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
*
- * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+ * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
*/
- private final long[] mMeasuredEnergySnapshots;
+ private final SparseLongArray mMeasuredEnergySnapshots;
/**
- * Constructor that initializes to the given energyArray;
- * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+ * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+ * {@link EnergyConsumerType#OTHER} was updated.
+ * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+ * uid to an energy (UJ). That is,
+ * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+ *
+ * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+ * If an id is present but a uid is not present, that uid's energy is 0.
*/
- public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
- this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+ private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+ /**
+ * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+ * exist and what their details are.
+ */
+ MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+ mEnergyConsumers = idToConsumerMap;
+ mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+ mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+ mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
}
/**
- * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
- * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+ * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+ * custom energy buckets supported by the device.
*/
- @VisibleForTesting
- MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
- if (initialEnergyArray.size() > numSubsystems) {
- throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
- + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
- }
- mMeasuredEnergySnapshots = new long[numSubsystems];
- Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
- fillGivenSubsystems(initialEnergyArray);
+ public int getNumOtherOrdinals() {
+ return mNumOtherOrdinals;
}
- /**
- * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
- * energy values from energyArray.
- */
- private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
- final int size = energyArray.size();
- for (int i = 0; i < size; i++) {
- final int subsystem = energyArray.getSubsystem(i);
- mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
- }
+ /** Class for returning measured energy delta data. */
+ static class MeasuredEnergyDeltaData {
+ /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+ public long displayEnergyUJ = UNAVAILABLE;
+
+ /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+ public @Nullable long[] otherTotalEnergyUJ = null;
+
+ /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+ public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
}
/**
* Update with the some freshly measured energies and return the difference (delta)
* between the previously stored values and the passed-in values.
*
- * @param energyArray measured energy array for some (possibly not all) subsystems.
+ * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+ * Consumers that are not present are ignored (they are *not* treated as 0).
*
- * @return a map from the updated subsystems to their corresponding energy deltas.
- * Subsystems not present in energyArray will not appear.
- * Subsystems with no difference in energy will not appear.
- * Returns null, if energyArray is null.
+ * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+ * their corresponding energy deltas.
+ * Fields with no interesting data (consumers not present in ecrs or with no energy
+ * difference) will generally be left as their default values.
+ * otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+ * length {@link #getNumOtherOrdinals()}.
+ * Returns null, if ecrs is null or empty.
*/
- public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
- if (energyArray == null) {
+ public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+ if (ecrs == null || ecrs.length == 0) {
return null;
}
- final SparseLongArray delta = new SparseLongArray();
- final int size = energyArray.size();
- for (int i = 0; i < size; i++) {
- final int updatedSubsystem = energyArray.getSubsystem(i);
- final long newEnergyUJ = energyArray.getEnergy(i);
- final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+ final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
- // If this is the first valid energy, there is no delta to take.
- if (oldEnergyUJ < 0) continue;
- final long deltaUJ = newEnergyUJ - oldEnergyUJ;
- if (deltaUJ == 0) continue;
- if (deltaUJ < 0) {
- Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
- + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+ for (final EnergyConsumerResult ecr : ecrs) {
+ // Extract the new energy data for the current consumer.
+ final int consumerId = ecr.id;
+ final long newEnergyUJ = ecr.energyUWs;
+ final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+ // Look up the static information about this consumer.
+ final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+ if (consumer == null) {
+ Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
continue;
}
- delta.put(updatedSubsystem, deltaUJ);
+ final int type = consumer.type;
+ final int ordinal = consumer.ordinal;
+
+ // Look up, and update, the old energy information about this consumer.
+ final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+ mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+ final SparseLongArray otherUidEnergies
+ = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+ // Everything is fully done being updated. We now calculate the delta for returning.
+
+ // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+ // there's no attribution either. Technically that isn't enforced at the HAL, but we
+ // can't really trust data like that anyway.
+
+ if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+ if (newEnergyUJ == oldEnergyUJ) continue;
+ final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+ if (deltaUJ < 0) {
+ Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+ + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+ continue;
+ }
+
+ switch (type) {
+ case EnergyConsumerType.DISPLAY:
+ output.displayEnergyUJ = deltaUJ;
+ break;
+ case EnergyConsumerType.OTHER:
+ if (output.otherTotalEnergyUJ == null) {
+ output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+ output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+ }
+ output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+ output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+ break;
+ default:
+ Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+ }
}
-
- fillGivenSubsystems(energyArray);
-
- return delta;
+ return output;
}
/**
- * Check if a subsystem's measured energy is available.
- * @param subsystem which subsystem.
- * @return true if subsystem is available.
+ * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+ * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+ * difference (delta) between the previously stored values and the passed-in values.
+ *
+ * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+ * @param newAttributions Record of uids and their new energyUJ values.
+ * Any uid not present is treated as having energy 0.
+ * If null or empty, all uids are treated as having energy 0.
+ * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+ * consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+ * Returns null if no delta available to calculate.
*/
- public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
- return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+ private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+ @NonNull EnergyConsumer consumerInfo,
+ @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+ if (consumerInfo.type != EnergyConsumerType.OTHER) {
+ return null;
+ }
+ if (newAttributions == null) {
+ // Treat null as empty (i.e. all uids have 0 energy).
+ newAttributions = new EnergyConsumerAttribution[0];
+ }
+
+ // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+ SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+ // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+ if (uidOldEnergyMap == null) {
+ uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+ mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+ for (EnergyConsumerAttribution newAttribution : newAttributions) {
+ uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+ }
+ return null;
+ }
+
+ // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+ final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+ for (EnergyConsumerAttribution newAttribution : newAttributions) {
+ final int uid = newAttribution.uid;
+ final long newEnergyUJ = newAttribution.energyUWs;
+ // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+ final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+ uidOldEnergyMap.put(uid, newEnergyUJ);
+
+ // Everything is fully done being updated. We now calculate the delta for returning.
+ if (oldEnergyUJ < 0) continue;
+ if (newEnergyUJ == oldEnergyUJ) continue;
+ final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+ if (deltaUJ < 0) {
+ Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+ + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+ continue;
+ }
+ uidEnergyDeltas.put(uid, deltaUJ);
+ }
+ return uidEnergyDeltas;
}
/** Dump debug data. */
public void dump(PrintWriter pw) {
- pw.println("Measured energy snapshot (microjoules):");
- pw.print(" ");
- for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
- final long energyUJ = mMeasuredEnergySnapshots[i];
- if (energyUJ == UNAVAILABLE) continue;
- pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
- pw.print(" : ");
- pw.print(energyUJ);
- if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
- pw.print(", ");
- }
+ pw.println("Measured energy snapshot");
+ pw.println("List of EnergyConsumers:");
+ for (int i = 0; i < mEnergyConsumers.size(); i++) {
+ final int id = mEnergyConsumers.keyAt(i);
+ final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+ pw.println(String.format(" Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+ consumer.id, consumer.ordinal, consumer.type, consumer.name));
}
+ pw.println("Map of consumerIds to energy (in microjoules):");
+ for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
+ final int id = mMeasuredEnergySnapshots.keyAt(i);
+ final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+ pw.println(String.format(" Consumer %d has energy %d uJ}", id, energyUJ));
+ }
+ pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+ pw.println(" " + mAttributionSnapshots);
pw.println();
}
+
+ /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+ private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+ if (idToConsumer == null) return 0;
+ int numOtherOrdinals = 0;
+ final int size = idToConsumer.size();
+ for (int idx = 0; idx < size; idx++) {
+ final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+ if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+ }
+ return numOtherOrdinals;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0576345..172f707 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2407,6 +2407,11 @@
app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
false, false,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
+
+ if (Process.createProcessGroup(uid, startResult.pid) < 0) {
+ Slog.e(ActivityManagerService.TAG, "Unable to create process group for "
+ + app.processName + " (" + startResult.pid + ")");
+ }
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index e533cc3..47573f3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -607,7 +607,8 @@
@GuardedBy("mService")
void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
synchronized (mProfilerLock) {
- pw.print(" lastPssTime=");
+ pw.print(prefix);
+ pw.print("lastPssTime=");
TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
pw.print(" pssProcState=");
pw.print(mPssProcState);
@@ -629,9 +630,8 @@
DebugUtils.printSizeValue(pw, mLastRss * 1024);
pw.println();
pw.print(prefix);
- pw.print(" trimMemoryLevel=");
+ pw.print("trimMemoryLevel=");
pw.println(mTrimMemoryLevel);
- pw.println();
pw.print(prefix); pw.print("procStateMemTracker: ");
mProcStateMemTracker.dumpLine(pw);
pw.print(prefix);
@@ -653,5 +653,6 @@
pw.print(" timeUsed=");
TimeUtils.formatDuration(mCurCpuTime.get() - lastCpuTime, pw);
}
+ pw.println();
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index da8aeb5..42e7ff4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -425,9 +425,10 @@
pw.print(prefix); pw.print("mInstr="); pw.println(mInstr);
}
pw.print(prefix); pw.print("thread="); pw.println(mThread);
- pw.print(prefix); pw.print("pid="); pw.print(mPid);
+ pw.print(prefix); pw.print("pid="); pw.println(mPid);
pw.print(prefix); pw.print("lastActivityTime=");
TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
+ pw.println();
if (mPersistent || mRemoved) {
pw.print(prefix); pw.print("persistent="); pw.print(mPersistent);
pw.print(" removed="); pw.println(mRemoved);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index e1a153d..499fbcb 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1277,7 +1277,7 @@
pw.print(" set="); pw.println(mSetAdj);
pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
pw.print(" setSchedGroup="); pw.print(mSetSchedGroup);
- pw.print(" systemNoUi="); pw.print(mSystemNoUi);
+ pw.print(" systemNoUi="); pw.println(mSystemNoUi);
pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
pw.print(" mRepProcState="); pw.print(mRepProcState);
pw.print(" setProcState="); pw.print(mSetProcState);
@@ -1297,7 +1297,7 @@
}
if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) {
pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
- pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean());
+ pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean());
}
pw.print(prefix); pw.print("cached="); pw.print(mCached);
pw.print(" empty="); pw.println(mEmpty);
@@ -1316,12 +1316,12 @@
}
if (mHasForegroundActivities || mRepForegroundActivities) {
pw.print(prefix);
- pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
+ pw.print("foregroundActivities="); pw.print(mHasForegroundActivities);
pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")");
}
if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) {
pw.print(prefix);
- pw.print(" whenUnimportant=");
+ pw.print("whenUnimportant=");
TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
pw.println();
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 10fe1e1..c92abd4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3131,11 +3131,6 @@
return AppOpsManager.MODE_ERRORED;
}
final Op op = getOpLocked(ops, code, uid, true);
- if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
- AppOpsManager.MODE_IGNORED);
- return AppOpsManager.MODE_IGNORED;
- }
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
@@ -3145,6 +3140,12 @@
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
+ if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+ attributedOp.rejected(uidState.state, flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+ AppOpsManager.MODE_IGNORED);
+ return AppOpsManager.MODE_IGNORED;
+ }
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 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/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index c956043..f888200 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -257,11 +257,15 @@
mUserId,
mOpPackageName,
mOperationId);
+ mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
+ } else {
+ // The UI was already showing :)
+ mState = STATE_AUTH_STARTED_UI_SHOWING;
}
- mState = STATE_AUTH_STARTED;
+
}
}
@@ -794,7 +798,7 @@
case BiometricAuthenticator.TYPE_FINGERPRINT:
return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
case BiometricAuthenticator.TYPE_FACE:
- return FaceManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
+ return FaceManager.getAuthHelpMessage(mContext, acquiredInfo, vendorCode);
default:
return null;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index baeb3fd..4925ce0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -435,13 +435,7 @@
mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> {
- if (mTestHalEnabled) {
- return new TestSession(mCurrentSession.mHalSessionCallback);
- } else {
- return mCurrentSession != null ? mCurrentSession.mSession : null;
- }
- };
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}
@NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -496,6 +490,11 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled: " + enabled);
+ if (enabled != mTestHalEnabled) {
+ // The framework should retrieve a new session from the HAL.
+ mCurrentSession = null;
+ }
mTestHalEnabled = enabled;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 1d57073..ff65c93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -17,92 +17,119 @@
package com.android.server.biometrics.sensors.face.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.Error;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.ISessionCallback;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.common.NativeHandle;
import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only no-ops.
*/
public class TestHal extends IFace.Stub {
+ private static final String TAG = "face.aidl.TestHal";
@Override
public SensorProps[] getSensorProps() {
+ Slog.w(TAG, "getSensorProps");
return new SensorProps[0];
}
@Override
public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
- return new ISession() {
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
+ Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+ return new ISession.Stub() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) {
-
+ public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ cb.onChallengeRevoked(challenge);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
byte enrollmentType, byte[] features, NativeHandle previewSurface) {
- return null;
+ Slog.w(TAG, "enroll, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
- return null;
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
- return null;
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
- public void enumerateEnrollments(int cookie) {
-
+ public void enumerateEnrollments(int cookie) throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getFeatures(int cookie, int enrollmentId) {
-
+ public void getFeatures(int cookie, int enrollmentId) throws RemoteException {
+ Slog.w(TAG, "getFeatures, cookie: " + cookie);
+ cb.onFeaturesRetrieved(new byte[0], enrollmentId);
}
@Override
public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
- byte feature, boolean enabled) {
-
+ byte feature, boolean enabled) throws RemoteException {
+ Slog.w(TAG, "setFeature, cookie: " + cookie);
+ cb.onFeatureSet(enrollmentId, feature);
}
@Override
- public void getAuthenticatorId(int cookie) {
-
+ public void getAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) {
-
+ public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
-
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
+ public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ cb.onLockoutCleared();
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
deleted file mode 100644
index 23e6988..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.face.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.face.ISession;
-import android.hardware.common.NativeHandle;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-public class TestSession extends ISession.Stub {
- private static final String TAG = "FaceTestSession";
-
- @NonNull
- private final Sensor.HalSessionCallback mHalSessionCallback;
-
- TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
- mHalSessionCallback = halSessionCallback;
- }
-
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
- mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
- }
-
- @Override
- public void revokeChallenge(int cookie, long challenge) {
- mHalSessionCallback.onChallengeRevoked(challenge);
- }
-
- @Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType,
- byte[] features, NativeHandle previewSurface) {
- return null;
- }
-
- @Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- return new ICancellationSignal() {
- @Override
- public void cancel() {
- mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
- };
- }
-
- @Override
- public ICancellationSignal detectInteraction(int cookie) {
- return null;
- }
-
- @Override
- public void enumerateEnrollments(int cookie) {
-
- }
-
- @Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
- }
-
- @Override
- public void getFeatures(int cookie, int enrollmentId) {
-
- }
-
- @Override
- public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature,
- boolean enabled) {
-
- }
-
- @Override
- public void getAuthenticatorId(int cookie) {
- Slog.d(TAG, "getAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdRetrieved(0);
- }
-
- @Override
- public void invalidateAuthenticatorId(int cookie) {
- Slog.d(TAG, "invalidateAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdInvalidated(0);
- }
-
- @Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
- mHalSessionCallback.onLockoutCleared();
- }
-}
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 bab1114d..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,17 +18,19 @@
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;
import java.util.ArrayList;
public class TestHal extends IBiometricsFace.Stub {
+ private static final String TAG = "face.hidl.TestHal";
@Nullable
private IBiometricsFaceClientCallback mCallback;
@@ -47,6 +49,7 @@
@Override
public OptionalUint64 generateChallenge(int challengeTimeoutSec) {
+ Slog.w(TAG, "generateChallenge");
final OptionalUint64 result = new OptionalUint64();
result.status = Status.OK;
result.value = 0;
@@ -55,6 +58,7 @@
@Override
public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) {
+ Slog.w(TAG, "enroll");
return 0;
}
@@ -94,17 +98,23 @@
}
@Override
- public int enumerate() {
+ public int enumerate() throws RemoteException {
+ Slog.w(TAG, "enumerate");
+ if (mCallback != null) {
+ mCallback.onEnumerate(0 /* deviceId */, new ArrayList<>(), 0 /* userId */);
+ }
return 0;
}
@Override
public int remove(int faceId) {
+ Slog.w(TAG, "remove");
return 0;
}
@Override
public int authenticate(long operationId) {
+ Slog.w(TAG, "authenticate");
return 0;
}
@@ -115,18 +125,8 @@
@Override
public int resetLockout(ArrayList<Byte> hat) {
+ Slog.w(TAG, "resetLockout");
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/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 7e4ee9e7..c83c0fb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -415,13 +415,7 @@
mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> {
- if (mTestHalEnabled) {
- return new TestSession(mCurrentSession.mHalSessionCallback);
- } else {
- return mCurrentSession != null ? mCurrentSession.mSession : null;
- }
- };
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}
@NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -476,6 +470,11 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled: " + enabled);
+ if (enabled != mTestHalEnabled) {
+ // The framework should retrieve a new session from the HAL.
+ mCurrentSession = null;
+ }
mTestHalEnabled = enabled;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index a31bcdc..8ed24b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -17,94 +17,120 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only provides no-ops.
*/
public class TestHal extends IFingerprint.Stub {
+ private static final String TAG = "fingerprint.aidl.TestHal";
+
@Override
public SensorProps[] getSensorProps() {
+ Slog.w(TAG, "getSensorProps");
return new SensorProps[0];
}
@Override
public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
- return new ISession() {
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
+ Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+ return new ISession.Stub() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) {
-
+ public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ cb.onChallengeRevoked(challenge);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
- return null;
+ Slog.w(TAG, "enroll, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
- return null;
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
- return null;
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
- public void enumerateEnrollments(int cookie) {
-
+ public void enumerateEnrollments(int cookie) throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getAuthenticatorId(int cookie) {
-
+ public void getAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) {
-
+ public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+ public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ cb.onLockoutCleared();
}
@Override
public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
+ Slog.w(TAG, "onPointerDown");
}
@Override
public void onPointerUp(int pointerId) {
-
+ Slog.w(TAG, "onPointerUp");
}
@Override
public void onUiReady() {
-
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
+ Slog.w(TAG, "onUiReady");
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
deleted file mode 100644
index ac4f665..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-class TestSession extends ISession.Stub {
-
- private static final String TAG = "FingerprintTestSession";
-
- @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
-
- TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
- mHalSessionCallback = halSessionCallback;
- }
-
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
- mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
- }
-
- @Override
- public void revokeChallenge(int cookie, long challenge) {
- mHalSessionCallback.onChallengeRevoked(challenge);
- }
-
- @Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
- return null;
- }
-
- @Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- return new ICancellationSignal() {
- @Override
- public void cancel() {
- mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
- };
- }
-
- @Override
- public ICancellationSignal detectInteraction(int cookie) {
- return null;
- }
-
- @Override
- public void enumerateEnrollments(int cookie) {
- Slog.d(TAG, "enumerate");
- }
-
- @Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
- Slog.d(TAG, "remove");
- }
-
- @Override
- public void getAuthenticatorId(int cookie) {
- Slog.d(TAG, "getAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdRetrieved(0);
- }
-
- @Override
- public void invalidateAuthenticatorId(int cookie) {
- Slog.d(TAG, "invalidateAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdInvalidated(0);
- }
-
- @Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
- mHalSessionCallback.onLockoutCleared();
- }
-
- @Override
- public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
- }
-
- @Override
- public void onPointerUp(int pointerId) {
-
- }
-
- @Override
- public void onUiReady() {
-
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index b7aec0e..14fdb50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -21,11 +21,14 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only provides no-ops.
*/
public class TestHal extends IBiometricsFingerprint.Stub {
+ private static final String TAG = "fingerprint.hidl.TestHal";
+
@Nullable
private IBiometricsFingerprintClientCallback mCallback;
@@ -57,6 +60,7 @@
@Override
public int enroll(byte[] hat, int gid, int timeoutSec) {
+ Slog.w(TAG, "enroll");
return 0;
}
@@ -79,12 +83,18 @@
}
@Override
- public int enumerate() {
+ public int enumerate() throws RemoteException {
+ Slog.w(TAG, "Enumerate");
+ if (mCallback != null) {
+ mCallback.onEnumerate(0 /* deviceId */, 0 /* fingerId */, 0 /* groupId */,
+ 0 /* remaining */);
+ }
return 0;
}
@Override
public int remove(int gid, int fid) {
+ Slog.w(TAG, "Remove");
return 0;
}
@@ -95,6 +105,7 @@
@Override
public int authenticate(long operationId, int gid) {
+ Slog.w(TAG, "Authenticate");
return 0;
}
}
\ No newline at end of file
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/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 6f112d7..508739f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -79,7 +79,6 @@
// server.
public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS";
public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS";
- public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
// The context is for the current user (system server)
private final Context mContext;
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 87b4c16..7ef315c 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,7 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.telephony.data.EpsBearerQosSessionAttributes;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.util.CollectionUtils;
import com.android.server.ConnectivityService;
@@ -260,18 +260,18 @@
}
private static void log(final String msg) {
- Slog.d(TAG, msg);
+ Log.d(TAG, msg);
}
private static void logw(final String msg) {
- Slog.w(TAG, msg);
+ Log.w(TAG, msg);
}
private static void loge(final String msg) {
- Slog.e(TAG, msg);
+ Log.e(TAG, msg);
}
private static void logwtf(final String msg) {
- Slog.wtf(TAG, msg);
+ Log.wtf(TAG, msg);
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 33c19b1..a769e88 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,10 +21,10 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
import android.Manifest;
import android.annotation.NonNull;
@@ -172,6 +172,12 @@
*/
@VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB
+ /**
+ * Network score that VPNs will announce to ConnectivityService.
+ * TODO: remove when the network scoring refactor lands.
+ */
+ private static final int VPN_DEFAULT_SCORE = 101;
+
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -496,6 +502,11 @@
updateAlwaysOnNotification(detailedState);
}
+ private void resetNetworkCapabilities() {
+ mNetworkCapabilities.setUids(null);
+ mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
+ }
+
/**
* Chooses whether to force all connections to go though VPN.
*
@@ -520,6 +531,11 @@
}
}
+ /** Returns the package name that is currently prepared. */
+ public String getPackage() {
+ return mPackage;
+ }
+
/**
* Check whether to prevent all traffic outside of a VPN even when the VPN is not connected.
*
@@ -930,8 +946,7 @@
agentDisconnect();
jniReset(mInterface);
mInterface = null;
- mNetworkCapabilities.setUids(null);
- mNetworkCapabilities.setTransportInfo(null);
+ resetNetworkCapabilities();
}
// Revoke the connection or stop the VpnRunner.
@@ -1229,8 +1244,7 @@
}
mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
- mNetworkCapabilities, lp,
- ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
+ mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
@@ -1744,8 +1758,7 @@
private void cleanupVpnStateLocked() {
mStatusIntent = null;
- mNetworkCapabilities.setUids(null);
- mNetworkCapabilities.setTransportInfo(null);
+ resetNetworkCapabilities();
mConfig = null;
mInterface = null;
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index e496d77..e693bcc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -16,9 +16,14 @@
package com.android.server.devicestate;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
import android.annotation.IntRange;
import android.annotation.NonNull;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
@@ -35,24 +40,25 @@
*/
public final class DeviceState {
/** Unique identifier for the device state. */
- @IntRange(from = 0)
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
private final int mIdentifier;
/** String description of the device state. */
@NonNull
private final String mName;
- public DeviceState(@IntRange(from = 0) int identifier,
+ public DeviceState(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
@NonNull String name) {
- if (identifier < 0) {
- throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
- }
+ Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
+ "identifier");
+
mIdentifier = identifier;
mName = name;
}
/** Returns the unique identifier for the device state. */
- @IntRange(from = 0)
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
public int getIdentifier() {
return mIdentifier;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 984a176..b3a6f26 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
import android.annotation.IntRange;
@@ -89,7 +91,7 @@
// the current state after the initial callback from the DeviceStateProvider.
@GuardedBy("mLock")
@NonNull
- private DeviceState mCommittedState = new DeviceState(0, "UNSET");
+ private DeviceState mCommittedState = new DeviceState(MINIMUM_DEVICE_STATE, "UNSET");
// The device state that is currently awaiting callback from the policy to be committed.
@GuardedBy("mLock")
@NonNull
@@ -598,8 +600,9 @@
}
@Override
- public void onStateChanged(@IntRange(from = 0) int identifier) {
- if (identifier < 0) {
+ public void onStateChanged(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) {
+ if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 2d4377f..109bf63 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,6 +16,9 @@
package com.android.server.devicestate;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
import android.annotation.IntRange;
/**
@@ -65,8 +68,10 @@
*
* @param identifier the identifier of the new device state.
*
- * @throws IllegalArgumentException if the state is less than 0.
+ * @throws IllegalArgumentException if the state is less than {@link MINIMUM_DEVICE_STATE}
+ * or greater than {@link MAXIMUM_DEVICE_STATE}.
*/
- void onStateChanged(@IntRange(from = 0) int identifier);
+ void onStateChanged(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier);
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
import android.util.Slog;
import android.util.TimeUtils;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
import android.util.Slog;
import android.util.Spline;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.server.display.utils.Plog;
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 0000000..d4556ed
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+ private static final String TAG = "DeviceStateToLayoutMap";
+
+ public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+ // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+ public static final int STATE_FOLDED = 100;
+ public static final int STATE_UNFOLDED = 101;
+
+ private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+ DeviceStateToLayoutMap(Context context) {
+ mLayoutMap.append(STATE_DEFAULT, new Layout());
+ loadFoldedDisplayConfig(context);
+ }
+
+ public void dumpLocked(IndentingPrintWriter ipw) {
+ ipw.println("DeviceStateToLayoutMap:");
+ ipw.increaseIndent();
+
+ ipw.println("Registered Layouts:");
+ for (int i = 0; i < mLayoutMap.size(); i++) {
+ ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+ }
+ }
+
+ Layout get(int state) {
+ Layout layout = mLayoutMap.get(state);
+ if (layout == null) {
+ layout = mLayoutMap.get(STATE_DEFAULT);
+ }
+ return layout;
+ }
+
+ private Layout create(int state) {
+ if (mLayoutMap.contains(state)) {
+ Slog.e(TAG, "Attempted to create a second layout for state " + state);
+ return null;
+ }
+
+ final Layout layout = new Layout();
+ mLayoutMap.append(state, layout);
+ return layout;
+ }
+
+ private void loadFoldedDisplayConfig(Context context) {
+ final String[] strDisplayIds = context.getResources().getStringArray(
+ com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+
+ if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
+ || TextUtils.isEmpty(strDisplayIds[1])) {
+ Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+ + "]");
+ return;
+ }
+
+ final long[] displayIds;
+ try {
+ displayIds = new long[] {
+ Long.parseLong(strDisplayIds[0]),
+ Long.parseLong(strDisplayIds[1])
+ };
+ } catch (NumberFormatException nfe) {
+ Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
+ return;
+ }
+
+ final int[] foldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ // Only add folded states if folded state config is not empty
+ if (foldedDeviceStates.length == 0) {
+ return;
+ }
+
+ // Create the folded state layout
+ final Layout foldedLayout = create(STATE_FOLDED);
+ foldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+ // Create the unfolded state layout
+ final Layout unfoldedLayout = create(STATE_UNFOLDED);
+ unfoldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
import android.util.Slog;
import android.view.DisplayAddress;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
import android.view.RoundedCorners;
import android.view.Surface;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import java.util.Arrays;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb..57f4486 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayAddress;
import com.android.internal.annotations.GuardedBy;
import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@
}
}
- public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- final DisplayDevice d = mDisplayDevices.get(i);
- if (uniqueId.equals(d.getUniqueId())) {
- return d;
+ public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+ for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+ final DisplayDevice device = mDisplayDevices.get(i);
+ if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+ return device;
}
}
return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..6a22941 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
import android.view.Surface;
import android.view.SurfaceControl;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
@@ -1188,6 +1188,7 @@
final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
final int state;
final int displayId = display.getDisplayIdLocked();
+
if (display.isEnabled()) {
state = mDisplayStates.get(displayId);
} else {
@@ -1564,12 +1565,6 @@
}
}
- void setFoldOverride(Boolean isFolded) {
- synchronized (mSyncRoot) {
- mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
- }
- }
-
private void clearViewportsLocked() {
mViewports.clear();
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
- case "set-fold":
- return setFoldOverride();
default:
return handleDefaultCommands(cmd);
}
@@ -92,8 +90,6 @@
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
- pw.println(" set-fold [fold|unfold|reset]");
- pw.println(" Simulates the 'fold' state of a device. 'reset' for default behavior.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -165,26 +161,4 @@
mService.setAmbientColorTemperatureOverride(cct);
return 0;
}
-
- private int setFoldOverride() {
- String state = getNextArg();
- if (state == null) {
- getErrPrintWriter().println("Error: no parameter specified for set-fold");
- return 1;
- }
- final Boolean isFolded;
- if ("fold".equals(state)) {
- isFolded = true;
- } else if ("unfold".equals(state)) {
- isFolded = false;
- } else if ("reset".equals(state)) {
- isFolded = null;
- } else {
- getErrPrintWriter().println("Error: Invalid fold state request: " + state);
- return 1;
- }
-
- mService.setFoldOverride(isFolded);
- return 0;
- }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
import android.util.TimeUtils;
import android.view.Display;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
import android.view.Choreographer;
import android.view.Display;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
import android.view.RoundedCorners;
import android.view.SurfaceControl;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d2..20b133c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.Objects;
@@ -718,6 +719,7 @@
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);
+ pw.println("mIsEnabled=" + mIsEnabled);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
}
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ dumpLocked(new PrintWriter(sw));
+ return sw.toString();
+ }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b26..a054db5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
+import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -75,40 +75,25 @@
private final boolean mSingleDisplayDemoMode;
/**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code true}.
- */
- private String mDisplayIdToUseWhenFolded;
-
- /**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code false}.
- */
- private String mDisplayIdToUseWhenUnfolded;
-
- /** Overrides the folded state of the device. For use with ADB commands. */
- private Boolean mIsFoldedOverride;
-
- /** Saves the last device fold state. */
- private boolean mIsFolded;
-
- /**
* List of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
* TODO: multi-display - Move the aforementioned comment?
*/
private final SparseArray<LogicalDisplay> mLogicalDisplays =
new SparseArray<LogicalDisplay>();
- private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
- private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
/** A mapping from logical display id to display group. */
private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
private final DisplayDeviceRepository mDisplayDeviceRepo;
+ private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener;
private final int[] mFoldedDeviceStates;
+ private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ private Layout mCurrentLayout = null;
+ private boolean mIsFolded = false;
+
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
mListener = listener;
@@ -118,7 +103,7 @@
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
- loadFoldedDisplayConfig(context);
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
}
@Override
@@ -213,13 +198,12 @@
ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
- ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+ ipw.println("mCurrentLayout=" + mCurrentLayout);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
for (int i = 0; i < logicalDisplayCount; i++) {
int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@
ipw.decreaseIndent();
ipw.println();
}
+ mDeviceStateToLayoutMap.dumpLocked(ipw);
}
void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@
void setDeviceFoldedLocked(boolean isFolded) {
mIsFolded = isFolded;
- if (mIsFoldedOverride != null) {
- isFolded = mIsFoldedOverride.booleanValue();
+
+ // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+ final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+ : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+ if (DEBUG) {
+ Slog.d(TAG, "New device state: " + state);
}
- if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
- || mLogicalDisplays.size() < 2) {
- // Do nothing if this behavior is disabled or there are less than two displays.
+ final Layout layout = mDeviceStateToLayoutMap.get(state);
+ if (layout == null) {
+ return;
+ }
+ final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final DisplayDevice newDefaultDevice =
+ mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+ if (newDefaultDevice == null) {
return;
}
- final DisplayDevice deviceFolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
- final DisplayDevice deviceUnfolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
- if (deviceFolded == null || deviceUnfolded == null) {
- // If the expected devices for folding functionality are not present, return early.
- return;
- }
-
- // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
- final LogicalDisplay displayFolded = getLocked(deviceFolded);
- final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
- if (displayFolded == null || displayUnfolded == null) {
- // If the expected displays are not present, return early.
- return;
- }
-
- // Find out which display is currently default and which is disabled.
final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
- final LogicalDisplay disabledDisplay;
- if (defaultDisplay == displayFolded) {
- disabledDisplay = displayUnfolded;
- } else if (defaultDisplay == displayUnfolded) {
- disabledDisplay = displayFolded;
- } else {
- // If neither folded or unfolded displays are currently set to the default display, we
- // are in an unknown state and it's best to log the error and bail.
- Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
- + " configured displays were set up as the default display. Default: "
- + defaultDisplay.getDisplayInfoLocked() + ", ConfiguredDisplays: [ folded="
- + displayFolded.getDisplayInfoLocked() + ", unfolded="
- + displayUnfolded.getDisplayInfoLocked() + " ]");
+ mCurrentLayout = layout;
+
+ // If we're already set up accurately, return early
+ if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
return;
}
- if (isFolded == (defaultDisplay == displayFolded)) {
- // Nothing to do, already in the right state.
+ // We need to swap the default display's display-device with the one that is supposed
+ // to be the default in the new layout.
+ final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+ if (displayToSwap == null) {
+ Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+ + newDefaultDevice);
return;
}
-
- // Everything was checked and we need to swap, lets swap.
- displayFolded.swapDisplaysLocked(displayUnfolded);
+ defaultDisplay.swapDisplaysLocked(displayToSwap);
// We ensure that the non-default Display is always forced to be off. This was likely
// already done in a previous iteration, but we do it with each swap in case something in
// the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
defaultDisplay.setEnabled(true);
- disabledDisplay.setEnabled(false);
+ displayToSwap.setEnabled(false);
// Update the world
updateLogicalDisplaysLocked();
-
- if (DEBUG) {
- Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
- + defaultDisplay.getDisplayInfoLocked());
- }
- }
-
- void setFoldOverrideLocked(Boolean isFolded) {
- if (!Objects.equals(isFolded, mIsFoldedOverride)) {
- mIsFoldedOverride = isFolded;
- setDeviceFoldedLocked(mIsFolded);
- }
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@
return;
}
- final int displayId = assignDisplayIdLocked(isDefault);
+ final int displayId = Layout.assignDisplayIdLocked(isDefault);
final int layerStack = assignLayerStackLocked(displayId);
final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@
return;
}
- mLogicalDisplays.put(displayId, display);
+ // For foldable devices, we start the internal non-default displays as disabled.
+ // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+ // display-device addition.
+ if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+ && !isDefault) {
+ display.setEnabled(false);
+ }
+ mLogicalDisplays.put(displayId, display);
displayGroup.addDisplayLocked(display);
mDisplayIdToGroupMap.append(displayId, displayGroup);
@@ -375,6 +343,10 @@
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "New Display added: " + display);
+ }
}
/**
@@ -466,10 +438,6 @@
}
}
- private int assignDisplayIdLocked(boolean isDefault) {
- return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
- }
-
private int assignDisplayGroupIdLocked(boolean isDefault) {
return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
}
@@ -480,21 +448,6 @@
return displayId;
}
- private void loadFoldedDisplayConfig(Context context) {
- final String[] displayIds = context.getResources().getStringArray(
- com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
- if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
- || TextUtils.isEmpty(displayIds[1])) {
- Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
- + "]");
- return;
- }
-
- mDisplayIdToUseWhenFolded = displayIds[0];
- mDisplayIdToUseWhenUnfolded = displayIds[1];
- }
-
public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
import android.util.FloatProperty;
import android.view.Choreographer;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
/**
* A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 0000000..18f39e6
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * 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.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+ private static final String TAG = "Layout";
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+ private final List<Display> mDisplays = new ArrayList<>(2);
+
+ /**
+ * @return The default display ID, or a new unique one to use.
+ */
+ public static int assignDisplayIdLocked(boolean isDefault) {
+ return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+ }
+
+ @Override
+ public String toString() {
+ return mDisplays.toString();
+ }
+
+ /**
+ * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+ *
+ * @param address Address of the device.
+ * @param isDefault Indicates if the device is meant to be the default display.
+ * @return The new layout.
+ */
+ public Display createDisplayLocked(
+ @NonNull DisplayAddress address, boolean isDefault) {
+ if (contains(address)) {
+ Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+ return null;
+ }
+
+ // See if we're dealing with the "default" display
+ if (isDefault && getById(DEFAULT_DISPLAY) != null) {
+ Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
+ isDefault = false;
+ }
+
+ // Assign a logical display ID and create the new display.
+ // Note that the logical display ID is saved into the layout, so when switching between
+ // different layouts, a logical display can be destroyed and later recreated with the
+ // same logical display ID.
+ final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+ final Display layout = new Display(address, logicalDisplayId);
+
+ mDisplays.add(layout);
+ return layout;
+ }
+
+ /**
+ * @param address The address to check.
+ *
+ * @return True if the specified address is used in this layout.
+ */
+ public boolean contains(@NonNull DisplayAddress address) {
+ final int size = mDisplays.size();
+ for (int i = 0; i < size; i++) {
+ if (address.equals(mDisplays.get(i).getAddress())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param id The display ID to check.
+ *
+ * @return The display corresponding to the specified display ID.
+ */
+ public Display getById(int id) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ Display layout = mDisplays.get(i);
+ if (id == layout.getLogicalDisplayId()) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param index The index of the display to return.
+ *
+ * @return the display at the specified index.
+ */
+ public Display getAt(int index) {
+ return mDisplays.get(index);
+ }
+
+ /**
+ * @return The number of displays defined for this layout.
+ */
+ public int size() {
+ return mDisplays.size();
+ }
+
+ /**
+ * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+ */
+ public static class Display {
+ private final DisplayAddress mAddress;
+ private final int mLogicalDisplayId;
+
+ Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+ mAddress = address;
+ mLogicalDisplayId = logicalDisplayId;
+ }
+
+ @Override
+ public String toString() {
+ return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+ }
+
+ public DisplayAddress getAddress() {
+ return mAddress;
+ }
+
+ public int getLogicalDisplayId() {
+ return mLogicalDisplayId;
+ }
+ }
+}
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/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/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 909fcda..66fc0d9 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -17,6 +17,7 @@
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.util.SparseIntArray;
@@ -108,7 +109,10 @@
private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
mPowerStatus.clear();
for (HdmiDeviceInfo info : deviceInfos) {
- mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+ if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+ || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+ }
}
}
@@ -117,19 +121,22 @@
localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
resetPowerStatus(deviceInfos);
for (HdmiDeviceInfo info : deviceInfos) {
- final int logicalAddress = info.getLogicalAddress();
- sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
- logicalAddress),
- new SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- // If fails to send <Give Device Power Status>,
- // update power status into UNKNOWN.
- if (error != SendMessageResult.SUCCESS) {
- updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+ if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+ || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ final int logicalAddress = info.getLogicalAddress();
+ sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+ logicalAddress),
+ new SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int error) {
+ // If fails to send <Give Device Power Status>,
+ // update power status into UNKNOWN.
+ if (error != SendMessageResult.SUCCESS) {
+ updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+ }
}
- }
- });
+ });
+ }
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..0754df0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -178,6 +178,7 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -5229,15 +5230,7 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- boolean asProto = false;
- for (int argIndex = 0; argIndex < args.length; argIndex++) {
- if (args[argIndex].equals(PROTO_ARG)) {
- asProto = true;
- break;
- }
- }
-
- if (asProto) {
+ if (ArrayUtils.contains(args, PROTO_ARG)) {
final ImeTracing imeTracing = ImeTracing.getInstance();
if (imeTracing.isEnabled()) {
imeTracing.stopTrace(null, false /* writeToFile */);
@@ -5246,12 +5239,7 @@
imeTracing.startTrace(null);
});
}
- }
- doDump(fd, pw, args, asProto);
- }
- private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
- if (useProto) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
proto.flush();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 28dc516..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);
@@ -1253,7 +1271,8 @@
ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
mEventLog.copyAggregateStats();
for (int i = 0; i < aggregateStats.size(); i++) {
- ipw.println(aggregateStats.keyAt(i));
+ ipw.print(aggregateStats.keyAt(i));
+ ipw.println(":");
ipw.increaseIndent();
ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
aggregateStats.valueAt(i);
@@ -1367,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;
@@ -1390,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();
}
@@ -1450,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/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 67060fc..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;
@@ -164,6 +165,12 @@
if (Build.IS_DEBUGGABLE || D) {
addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
}
+ 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. */
@@ -197,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:
@@ -331,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
@@ -397,6 +424,8 @@
private int mActiveRequestCount;
@GuardedBy("this")
private int mForegroundRequestCount;
+ @GuardedBy("this")
+ private int mDeliveredLocationCount;
@GuardedBy("this")
private long mFastestIntervalMs = Long.MAX_VALUE;
@@ -464,6 +493,10 @@
Preconditions.checkState(mForegroundRequestCount >= 0);
}
+ synchronized void markLocationDelivered() {
+ mDeliveredLocationCount++;
+ }
+
public synchronized void updateTotals() {
if (mAddedRequestCount > 0) {
long realtimeMs = SystemClock.elapsedRealtime();
@@ -488,7 +521,8 @@
+ intervalToString(mSlowestIntervalMs)
+ ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
+ "/" + formatDuration(mActiveTimeTotalMs) + "/"
- + formatDuration(mForegroundTimeTotalMs);
+ + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+ + mDeliveredLocationCount;
}
private static String intervalToString(long intervalMs) {
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/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..685e9e6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -279,6 +279,7 @@
super.onBootPhase(phase);
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mLockSettingsService.migrateOldDataAfterSystemReady();
+ mLockSettingsService.loadEscrowData();
}
}
@@ -824,13 +825,17 @@
mSpManager.initWeaverService();
getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady();
- mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
mInjector.getFaceManager());
}
+ private void loadEscrowData() {
+ mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
+ }
+
private void getAuthSecretHal() {
try {
mAuthSecretService = IAuthSecret.getService(/* retry */ true);
@@ -2372,10 +2377,17 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
+ final int origPid = Binder.getCallingPid();
+ final int origUid = Binder.getCallingUid();
+
+ // The original identity is an opaque integer.
final long origId = Binder.clearCallingIdentity();
+ Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
try {
- (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec(
- this, in, out, err, args, callback, resultReceiver);
+ final LockSettingsShellCommand command =
+ new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
+ origUid);
+ command.exec(this, in, out, err, args, callback, resultReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 834cf05..6a5c2d89 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -21,8 +21,11 @@
import android.app.ActivityManager;
import android.app.admin.PasswordMetrics;
+import android.content.Context;
import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.Slog;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -43,15 +46,25 @@
private static final String COMMAND_VERIFY = "verify";
private static final String COMMAND_GET_DISABLED = "get-disabled";
private static final String COMMAND_REMOVE_CACHE = "remove-cache";
+ private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE =
+ "set-resume-on-reboot-provider-package";
private static final String COMMAND_HELP = "help";
private int mCurrentUserId;
private final LockPatternUtils mLockPatternUtils;
+ private final Context mContext;
+ private final int mCallingPid;
+ private final int mCallingUid;
+
private String mOld = "";
private String mNew = "";
- LockSettingsShellCommand(LockPatternUtils lockPatternUtils) {
+ LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid,
+ int callingUid) {
mLockPatternUtils = lockPatternUtils;
+ mCallingPid = callingPid;
+ mCallingUid = callingUid;
+ mContext = context;
}
@Override
@@ -68,6 +81,7 @@
case COMMAND_HELP:
case COMMAND_GET_DISABLED:
case COMMAND_SET_DISABLED:
+ case COMMAND_SET_ROR_PROVIDER_PACKAGE:
break;
default:
getErrPrintWriter().println(
@@ -80,6 +94,9 @@
case COMMAND_REMOVE_CACHE:
runRemoveCache();
return 0;
+ case COMMAND_SET_ROR_PROVIDER_PACKAGE:
+ runSetResumeOnRebootProviderPackage();
+ return 0;
case COMMAND_HELP:
onHelp();
return 0;
@@ -171,6 +188,10 @@
pw.println(" remove-cache [--user USER_ID]");
pw.println(" Removes cached unified challenge for the managed profile.");
pw.println("");
+ pw.println(" set-resume-on-reboot-provider-package <package_name>");
+ pw.println(" Sets the package name for server based resume on reboot service "
+ + "provider.");
+ pw.println("");
}
}
@@ -256,6 +277,17 @@
return true;
}
+ private boolean runSetResumeOnRebootProviderPackage() {
+ final String packageName = mNew;
+ String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE;
+ Slog.i(TAG, "Setting " + name + " to " + packageName);
+
+ mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE,
+ mCallingPid, mCallingUid, TAG);
+ SystemProperties.set(name, packageName);
+ return true;
+ }
+
private boolean runClear() {
LockscreenCredential none = LockscreenCredential.createNone();
if (!isNewCredentialSufficient(none)) {
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 06962d4..30ea555 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.os.Handler;
import android.os.SystemClock;
import android.os.UserManager;
import android.provider.DeviceConfig;
@@ -39,6 +40,7 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import javax.crypto.SecretKey;
@@ -76,6 +78,13 @@
private static final int BOOT_COUNT_TOLERANCE = 5;
/**
+ * The default retry specs for loading reboot escrow data. We will attempt to retry loading
+ * escrow data on temporarily errors, e.g. unavailable network.
+ */
+ private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
+ private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+
+ /**
* Logs events for later debugging in bugreports.
*/
private final RebootEscrowEventLog mEventLog;
@@ -137,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();
@@ -148,6 +158,14 @@
return null;
}
+ void post(Handler handler, Runnable runnable) {
+ handler.post(runnable);
+ }
+
+ void postDelayed(Handler handler, Runnable runnable, long delayMillis) {
+ handler.postDelayed(runnable, delayMillis);
+ }
+
public Context getContext() {
return mContext;
}
@@ -199,7 +217,18 @@
mKeyStoreManager = injector.getKeyStoreManager();
}
- void loadRebootEscrowDataIfAvailable() {
+ private void onGetRebootEscrowKeyFailed(List<UserInfo> users) {
+ Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+
+ // Clear the old key in keystore.
+ mKeyStoreManager.clearKeyStoreEncryptionKey();
+ onEscrowRestoreComplete(false);
+ }
+
+ void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
List<UserInfo> users = mUserManager.getUsers();
List<UserInfo> rebootEscrowUsers = new ArrayList<>();
for (UserInfo user : users) {
@@ -212,17 +241,53 @@
return;
}
+ mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
+ retryHandler, 0, users, rebootEscrowUsers));
+ }
+
+ void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber,
+ List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+ Objects.requireNonNull(retryHandler);
+
+ final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
+ final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_interval_seconds",
+ DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+
+ if (attemptNumber < retryLimit) {
+ Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
+ mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry(
+ retryHandler, attemptNumber, users, rebootEscrowUsers),
+ retryIntervalInSeconds * 1000);
+ return;
+ }
+
+ Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+ onGetRebootEscrowKeyFailed(users);
+ }
+
+ void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber,
+ List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
// Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
// generated before reboot. Note that we will clear the escrow key even if the keystore key
// is null.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
- RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
- if (kk == null || escrowKey == null) {
- Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
- for (UserInfo user : users) {
- mStorage.removeRebootEscrow(user.id);
- }
- onEscrowRestoreComplete(false);
+ if (kk == null) {
+ Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
+ }
+
+ RebootEscrowKey escrowKey;
+ try {
+ escrowKey = getAndClearRebootEscrowKey(kk);
+ } catch (IOException e) {
+ scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
+ rebootEscrowUsers);
+ return;
+ }
+
+ if (escrowKey == null) {
+ onGetRebootEscrowKeyFailed(users);
return;
}
@@ -249,7 +314,7 @@
}
}
- private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
+ private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
if (rebootEscrowProvider == null) {
Slog.w(TAG,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 6c1040b..4b00772 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -33,7 +33,7 @@
* An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
*/
class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
- private static final String TAG = "RebootEscrowProvider";
+ private static final String TAG = "RebootEscrowProviderHal";
private final Injector mInjector;
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index 857ad5f..af6faad 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings;
+import java.io.IOException;
+
import javax.crypto.SecretKey;
/**
@@ -33,9 +35,10 @@
/**
* Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted,
- * use the input key to decrypt the RebootEscrowKey. Returns null on failure.
+ * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an
+ * IOException if the failure is non-fatal, and a retry may succeed.
*/
- RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey);
+ RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException;
/**
* Clears the stored RebootEscrowKey.
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index ba1a680..b3b4546 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -35,7 +35,7 @@
* encrypt & decrypt the blob.
*/
class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
- private static final String TAG = "RebootEscrowProvider";
+ private static final String TAG = "RebootEscrowProviderServerBased";
// Timeout for service binding
private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
@@ -50,6 +50,8 @@
private final Injector mInjector;
+ private byte[] mServerBlob;
+
static class Injector {
private ResumeOnRebootServiceConnection mServiceConnection = null;
@@ -124,17 +126,25 @@
}
@Override
- public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
- byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+ public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
+ if (mServerBlob == null) {
+ mServerBlob = mStorage.readRebootEscrowServerBlob();
+ }
// Delete the server blob in storage.
mStorage.removeRebootEscrowServerBlob();
- if (serverBlob == null) {
+ if (mServerBlob == null) {
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 {
- byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+ byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
if (escrowKeyBytes == null) {
Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
return null;
@@ -145,7 +155,7 @@
}
return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
- } catch (TimeoutException | RemoteException | IOException e) {
+ } catch (TimeoutException | RemoteException e) {
Slog.w(TAG, "Failed to decrypt the server blob ", e);
return null;
}
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index 5bd3f64..9c471b8 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -31,6 +31,7 @@
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.resumeonreboot.IResumeOnRebootService;
@@ -55,6 +56,10 @@
Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE;
private static final String TAG = "ResumeOnRebootServiceProvider";
+ // The system property name that overrides the default service provider package name.
+ static final String PROP_ROR_PROVIDER_PACKAGE =
+ "persist.sys.resume_on_reboot_provider_package";
+
private final Context mContext;
private final PackageManager mPackageManager;
@@ -72,12 +77,19 @@
private ServiceInfo resolveService() {
Intent intent = new Intent();
intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE);
- if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
- intent.setPackage(PROVIDER_PACKAGE);
+ int queryFlag = PackageManager.GET_SERVICES;
+ String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, "");
+ if (!testAppName.isEmpty()) {
+ Slog.i(TAG, "Using test app: " + testAppName);
+ intent.setPackage(testAppName);
+ } else {
+ queryFlag |= PackageManager.MATCH_SYSTEM_ONLY;
+ if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
+ intent.setPackage(PROVIDER_PACKAGE);
+ }
}
- List<ResolveInfo> resolvedIntents =
- mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag);
for (ResolveInfo resolvedInfo : resolvedIntents) {
if (resolvedInfo.serviceInfo != null
&& PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) {
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 6d1c680..3cc32be 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -16,10 +16,10 @@
package com.android.server.net;
-import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static android.provider.Settings.ACTION_VPN_SETTINGS;
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,43 +28,37 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.Network;
import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
import android.os.Handler;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
-import com.android.server.ConnectivityService;
-import com.android.server.EventLogTags;
import com.android.server.connectivity.Vpn;
import java.util.List;
import java.util.Objects;
/**
- * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
- * connected and kicks off VPN connection, managing any required {@code netd}
- * firewall rules.
+ * State tracker for legacy lockdown VPN. Watches for physical networks to be
+ * connected and kicks off VPN connection.
*/
public class LockdownVpnTracker {
private static final String TAG = "LockdownVpnTracker";
- /** Number of VPN attempts before waiting for user intervention. */
- private static final int MAX_ERROR_COUNT = 4;
-
public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
@NonNull private final Context mContext;
- @NonNull private final ConnectivityService mConnService;
+ @NonNull private final ConnectivityManager mCm;
@NonNull private final NotificationManager mNotificationManager;
@NonNull private final Handler mHandler;
@NonNull private final Vpn mVpn;
@@ -76,19 +70,73 @@
@NonNull private final PendingIntent mConfigIntent;
@NonNull private final PendingIntent mResetIntent;
+ @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback();
+ @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback();
+
+ private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+ private Network mNetwork = null;
+ private LinkProperties mLinkProperties = null;
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+ boolean networkChanged = false;
+ if (!network.equals(mNetwork)) {
+ // The default network just changed.
+ mNetwork = network;
+ networkChanged = true;
+ }
+ mLinkProperties = lp;
+ // Backwards compatibility: previously, LockdownVpnTracker only responded to connects
+ // and disconnects, not LinkProperties changes on existing networks.
+ if (networkChanged) {
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ // The default network has gone down.
+ mNetwork = null;
+ mLinkProperties = null;
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
+ }
+ }
+
+ private class VpnNetworkCallback extends NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+ @Override
+ public void onLost(Network network) {
+ onAvailable(network);
+ }
+ }
+
@Nullable
private String mAcceptedEgressIface;
- private int mErrorCount;
-
public LockdownVpnTracker(@NonNull Context context,
- @NonNull ConnectivityService connService,
@NonNull Handler handler,
@NonNull KeyStore keyStore,
@NonNull Vpn vpn,
@NonNull VpnProfile profile) {
mContext = Objects.requireNonNull(context);
- mConnService = Objects.requireNonNull(connService);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
mHandler = Objects.requireNonNull(handler);
mVpn = Objects.requireNonNull(vpn);
mProfile = Objects.requireNonNull(profile);
@@ -110,24 +158,20 @@
* connection when ready, or setting firewall rules once VPN is connected.
*/
private void handleStateChangedLocked() {
-
- final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
- final LinkProperties egressProp = mConnService.getActiveLinkProperties();
+ final Network network = mDefaultNetworkCallback.getNetwork();
+ final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
// Restart VPN when egress network disconnected or changed
- final boolean egressDisconnected = egressInfo == null
- || State.DISCONNECTED.equals(egressInfo.getState());
+ final boolean egressDisconnected = (network == null);
final boolean egressChanged = egressProp == null
|| !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
- final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType();
final String egressIface = (egressProp == null) ?
null : egressProp.getInterfaceName();
- Log.d(TAG, "handleStateChanged: egress=" + egressType
- + " " + mAcceptedEgressIface + "->" + egressIface);
+ Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface);
if (egressDisconnected || egressChanged) {
mAcceptedEgressIface = null;
@@ -138,46 +182,49 @@
return;
}
- if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
- EventLogTags.writeLockdownVpnError(egressType);
- }
-
- if (mErrorCount > MAX_ERROR_COUNT) {
- showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
-
- } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
- if (mProfile.isValidLockdownProfile()) {
- Log.d(TAG, "Active network connected; starting VPN");
- EventLogTags.writeLockdownVpnConnecting(egressType);
- showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
-
- mAcceptedEgressIface = egressProp.getInterfaceName();
- try {
- // Use the privileged method because Lockdown VPN is initiated by the system, so
- // no additional permission checks are necessary.
- mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
- } catch (IllegalStateException e) {
- mAcceptedEgressIface = null;
- Log.e(TAG, "Failed to start VPN", e);
- showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
- }
- } else {
+ // At this point, |network| is known to be non-null.
+ if (!vpnInfo.isConnectedOrConnecting()) {
+ if (!mProfile.isValidLockdownProfile()) {
Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+ return;
}
+ Log.d(TAG, "Active network connected; starting VPN");
+ showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
+
+ mAcceptedEgressIface = egressIface;
+ try {
+ // Use the privileged method because Lockdown VPN is initiated by the system, so
+ // no additional permission checks are necessary.
+ //
+ // Pass in the underlying network here because the legacy VPN is, in fact, tightly
+ // coupled to a given underlying network and cannot provide mobility. This makes
+ // things marginally more correct in two ways:
+ //
+ // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra
+ // CONNECTED broadcast for the underlying network type. The underlying type comes
+ // from here. LTT *could* assume that the underlying network is the default
+ // network, but that might introduce a race condition if, say, the VPN starts
+ // connecting on cell, but when the connection succeeds and the agent is
+ // registered, the default network is now wifi.
+ // 2. If no underlying network is passed in, then CS will assume the underlying
+ // network is the system default. So, if the VPN is up and underlying network
+ // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
+ // changed to match the new default network (e.g., cell).
+ mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
+ } catch (IllegalStateException e) {
+ mAcceptedEgressIface = null;
+ Log.e(TAG, "Failed to start VPN", e);
+ showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+ }
} else if (vpnInfo.isConnected() && vpnConfig != null) {
final String iface = vpnConfig.interfaze;
final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
Log.d(TAG, "VPN connected using iface=" + iface
+ ", sourceAddr=" + sourceAddrs.toString());
- EventLogTags.writeLockdownVpnConnected(egressType);
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
-
- final NetworkInfo clone = new NetworkInfo(egressInfo);
- augmentNetworkInfo(clone);
- mConnService.sendConnectedBroadcast(clone);
}
}
@@ -192,7 +239,15 @@
mVpn.setEnableTeardown(false);
mVpn.setLockdown(true);
+ mCm.setLegacyLockdownVpnEnabled(true);
handleStateChangedLocked();
+
+ mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+ final NetworkRequest vpnRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_VPN)
+ .build();
+ mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler);
}
public void shutdown() {
@@ -205,20 +260,21 @@
Log.d(TAG, "shutdownLocked()");
mAcceptedEgressIface = null;
- mErrorCount = 0;
mVpn.stopVpnRunnerPrivileged();
mVpn.setLockdown(false);
+ mCm.setLegacyLockdownVpnEnabled(false);
hideNotification();
mVpn.setEnableTeardown(true);
+ mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
+ mCm.unregisterNetworkCallback(mVpnNetworkCallback);
}
/**
* Reset VPN lockdown tracker. Called by ConnectivityService when receiving
* {@link #ACTION_LOCKDOWN_RESET} pending intent.
*/
- @GuardedBy("mConnService.mVpns")
public void reset() {
Log.d(TAG, "reset()");
synchronized (mStateLock) {
@@ -229,28 +285,6 @@
}
}
- public void onNetworkInfoChanged() {
- synchronized (mStateLock) {
- handleStateChangedLocked();
- }
- }
-
- public void onVpnStateChanged(NetworkInfo info) {
- if (info.getDetailedState() == DetailedState.FAILED) {
- mErrorCount++;
- }
- synchronized (mStateLock) {
- handleStateChangedLocked();
- }
- }
-
- public void augmentNetworkInfo(NetworkInfo info) {
- if (info.isConnected()) {
- final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
- info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
- }
- }
-
private void showNotification(int titleRes, int iconRes) {
final Notification.Builder builder =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN)
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 769b781..78c1a95 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.IBinder;
import android.os.IInterface;
@@ -197,6 +198,11 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ // nothing to filter
+ }
+
+ @Override
protected void loadDefaultsFromConfig() {
String defaultDndAccess = mContext.getResources().getString(
R.string.config_defaultDndAccessPackages);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 21537e6..bbdcac2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -189,6 +189,8 @@
abstract protected void onServiceAdded(ManagedServiceInfo info);
+ abstract protected void ensureFilters(ServiceInfo si, int userId);
+
protected List<ManagedServiceInfo> getServices() {
synchronized (mMutex) {
List<ManagedServiceInfo> services = new ArrayList<>(mServices);
@@ -1342,8 +1344,10 @@
for (ComponentName component : add) {
try {
ServiceInfo info = mPm.getServiceInfo(component,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ userId);
if (info == null) {
Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+ ": service not found");
@@ -1356,7 +1360,7 @@
}
Slog.v(TAG,
"enabling " + getCaption() + " for " + userId + ": " + component);
- registerService(component, userId);
+ registerService(info, userId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -1368,9 +1372,15 @@
* Version of registerService that takes the name of a service component to bind to.
*/
@VisibleForTesting
- void registerService(final ComponentName name, final int userid) {
+ void registerService(final ServiceInfo si, final int userId) {
+ ensureFilters(si, userId);
+ registerService(si.getComponentName(), userId);
+ }
+
+ @VisibleForTesting
+ void registerService(final ComponentName cn, final int userId) {
synchronized (mMutex) {
- registerServiceLocked(name, userid);
+ registerServiceLocked(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..917be29 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,6 +110,8 @@
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -175,6 +177,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -212,6 +215,7 @@
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
@@ -284,7 +288,6 @@
import com.android.server.notification.toast.TextToastRecord;
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
-import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -358,7 +361,7 @@
private static final int MESSAGE_RECONSIDER_RANKING = 1000;
private static final int MESSAGE_RANKING_SORT = 1001;
- static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+ static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds
// 1 second past the ANR timeout.
@@ -3056,7 +3059,7 @@
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
- showNextToastLocked();
+ showNextToastLocked(false);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -7392,7 +7395,7 @@
}
@GuardedBy("mToastQueue")
- void showNextToastLocked() {
+ void showNextToastLocked(boolean lastToastWasTextRecord) {
if (mIsCurrentToastShown) {
return; // Don't show the same toast twice.
}
@@ -7406,7 +7409,7 @@
mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
- scheduleDurationReachedLocked(record);
+ scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if (rateLimitingEnabled) {
mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
@@ -7477,7 +7480,7 @@
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
- showNextToastLocked();
+ showNextToastLocked(lastToast instanceof TextToastRecord);
}
}
@@ -7491,7 +7494,7 @@
}
@GuardedBy("mToastQueue")
- private void scheduleDurationReachedLocked(ToastRecord r)
+ private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
@@ -7501,6 +7504,14 @@
// preference.
delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
AccessibilityManager.FLAG_CONTENT_TEXT);
+
+ if (lastToastWasTextRecord) {
+ delay += 250; // delay to account for previous toast's "out" animation
+ }
+ if (r instanceof TextToastRecord) {
+ delay += 333; // delay to account for this toast's "in" animation
+ }
+
mHandler.sendMessageDelayed(m, delay);
}
@@ -8960,7 +8971,8 @@
NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey);
if (nls != null
&& (!nls.isTypeAllowed(notificationType)
- || !nls.isPackageAllowed(sbn.getPackageName()))) {
+ || !nls.isPackageAllowed(
+ new VersionedPackage(sbn.getPackageName(), sbn.getUid())))) {
return false;
}
return true;
@@ -9123,6 +9135,12 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ // nothing to filter; no user visible settings for types/packages like other
+ // listeners
+ }
+
+ @Override
@GuardedBy("mNotificationLock")
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
mListeners.unregisterService(removed.service, removed.userid);
@@ -9567,11 +9585,12 @@
public class NotificationListeners extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
- static final String TAG_REQUESTED_LISTENERS = "req_listeners";
+ static final String TAG_REQUESTED_LISTENERS = "request_listeners";
static final String TAG_REQUESTED_LISTENER = "listener";
static final String ATT_COMPONENT = "component";
static final String ATT_TYPES = "types";
- static final String ATT_PKGS = "pkgs";
+ static final String ATT_PKG = "pkg";
+ static final String ATT_UID = "uid";
static final String TAG_APPROVED = "allowed";
static final String TAG_DISALLOWED= "disallowed";
static final String XML_SEPARATOR = ",";
@@ -9689,28 +9708,6 @@
}
@Override
- public void onUserUnlocked(int user) {
- int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
-
- final PackageManager pmWrapper = mContext.getPackageManager();
- List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
- new Intent(getConfig().serviceInterface), flags, user);
-
- for (ResolveInfo resolveInfo : installedServices) {
- ServiceInfo info = resolveInfo.serviceInfo;
-
- if (!getConfig().bindPermission.equals(info.permission)) {
- continue;
- }
- Pair key = Pair.create(info.getComponentName(), user);
- if (!mRequestedNotificationListeners.containsKey(key)) {
- mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
- }
- }
- super.onUserUnlocked(user);
- }
-
- @Override
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
super.onPackagesChanged(removingPackage, pkgList, uidList);
@@ -9729,6 +9726,18 @@
}
}
}
+
+ // clean up anything in the disallowed pkgs list
+ for (int i = 0; i < pkgList.length; i++) {
+ String pkg = pkgList[i];
+ int userId = UserHandle.getUserId(uidList[i]);
+ for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
+ NotificationListenerFilter nlf = mRequestedNotificationListeners.valueAt(j);
+
+ VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
+ nlf.removePackage(ai);
+ }
+ }
}
@Override
@@ -9758,15 +9767,17 @@
int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING;
- ArraySet<String> disallowedPkgs = new ArraySet<>();
+ ArraySet<VersionedPackage> disallowedPkgs = new ArraySet<>();
final int listenerOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) {
if (TAG_APPROVED.equals(parser.getName())) {
approved = XmlUtils.readIntAttribute(parser, ATT_TYPES);
} else if (TAG_DISALLOWED.equals(parser.getName())) {
- String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS);
- if (!TextUtils.isEmpty(pkgs)) {
- disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR));
+ String pkg = XmlUtils.readStringAttribute(parser, ATT_PKG);
+ int uid = XmlUtils.readIntAttribute(parser, ATT_UID);
+ if (!TextUtils.isEmpty(pkg)) {
+ VersionedPackage ai = new VersionedPackage(pkg, uid);
+ disallowedPkgs.add(ai);
}
}
}
@@ -9791,10 +9802,14 @@
XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes());
out.endTag(null, TAG_APPROVED);
- out.startTag(null, TAG_DISALLOWED);
- XmlUtils.writeStringAttribute(
- out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages()));
- out.endTag(null, TAG_DISALLOWED);
+ for (VersionedPackage ai : nlf.getDisallowedPackages()) {
+ if (!TextUtils.isEmpty(ai.getPackageName())) {
+ out.startTag(null, TAG_DISALLOWED);
+ XmlUtils.writeStringAttribute(out, ATT_PKG, ai.getPackageName());
+ XmlUtils.writeIntAttribute(out, ATT_UID, ai.getVersionCode());
+ out.endTag(null, TAG_DISALLOWED);
+ }
+ }
out.endTag(null, TAG_REQUESTED_LISTENER);
}
@@ -9812,6 +9827,38 @@
mRequestedNotificationListeners.put(pair, nlf);
}
+ @Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ Pair listener = Pair.create(si.getComponentName(), userId);
+ NotificationListenerFilter existingNlf =
+ mRequestedNotificationListeners.get(listener);
+ if (existingNlf == null) {
+ // no stored filters for this listener; see if they provided a default
+ if (si.metaData != null) {
+ String typeList = si.metaData.getString(
+ NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES);
+ if (typeList != null) {
+ int types = 0;
+ String[] typeStrings = typeList.split(XML_SEPARATOR);
+ for (int i = 0; i < typeStrings.length; i++) {
+ if (TextUtils.isEmpty(typeStrings[i])) {
+ continue;
+ }
+ try {
+ types |= Integer.parseInt(typeStrings[i]);
+ } catch (NumberFormatException e) {
+ // skip
+ }
+ }
+
+ NotificationListenerFilter nlf =
+ new NotificationListenerFilter(types, new ArraySet<>());
+ mRequestedNotificationListeners.put(listener, nlf);
+ }
+ }
+ }
+ }
+
@GuardedBy("mNotificationLock")
public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
if (trim == TRIM_LIGHT) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b7b72d1..0b52c2e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -115,10 +115,10 @@
out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME.");
out.println(" disable [--user USER_ID] PACKAGE[:NAME]");
out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME.");
- out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE[:NAME]");
- out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME and");
- out.println(" disable all other overlays for its target package. If the --category");
- out.println(" option is given, only disables other overlays in the same category.");
+ out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE");
+ out.println(" Enable overlay within or owned by PACKAGE and disable all other overlays");
+ out.println(" for its target package. If the --category option is given, only disables");
+ out.println(" other overlays in the same category.");
out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
out.println(" Change the priority of the overlay to be just higher than");
out.println(" the priority of PARENT If PARENT is the special keyword");
@@ -325,27 +325,12 @@
return 1;
}
}
-
- final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
- final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId);
- if (overlayInfo == null) {
- err.println("Error: Unable to get overlay info of: " + overlay);
- return 1;
+ final String overlay = getNextArgRequired();
+ if (inCategory) {
+ return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
+ } else {
+ return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
}
-
- final List<OverlayInfo> overlaysForTarget =
- mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
- final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
- for (final OverlayInfo disableOverlay : overlaysForTarget) {
- if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
- || !disableOverlay.isMutable) {
- continue;
- }
- builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId);
- }
- builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId);
- mInterface.commit(builder.build());
- return 0;
}
private int runSetPriority() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index de85d9e..f31d1da 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -43,6 +43,7 @@
import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,12 @@
abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
/**
+ * Returns array of all staged sessions known to apexd.
+ */
+ @NonNull
+ abstract SparseArray<ApexSessionInfo> getSessions();
+
+ /**
* Submit a staged session to apex service. This causes the apex service to perform some initial
* verification and accept or reject the session. Submitting a session successfully is not
* enough for it to be activated at the next boot, the caller needs to call
@@ -691,6 +698,21 @@
}
@Override
+ SparseArray<ApexSessionInfo> getSessions() {
+ try {
+ final ApexSessionInfo[] sessions = waitForApexService().getSessions();
+ final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length);
+ for (int i = 0; i < sessions.length; i++) {
+ result.put(sessions[i].sessionId, sessions[i]);
+ }
+ return result;
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact apexservice", re);
+ throw new RuntimeException(re);
+ }
+ }
+
+ @Override
ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
@@ -1083,6 +1105,11 @@
}
@Override
+ SparseArray<ApexSessionInfo> getSessions() {
+ return new SparseArray<>(0);
+ }
+
+ @Override
ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
// trade-off worth doing to save boot time work.
int result = pm.performDexOptWithStatus(new DexoptOptions(
pkg,
- PackageManagerService.REASON_BOOT,
+ PackageManagerService.REASON_POST_BOOT,
DexoptOptions.DEXOPT_BOOT_COMPLETE));
if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 52fdc79..308e815 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -27,6 +27,8 @@
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -45,12 +47,20 @@
public class DataLoaderManagerService extends SystemService {
private static final String TAG = "DataLoaderManager";
private final Context mContext;
+ private final HandlerThread mThread;
+ private final Handler mHandler;
private final DataLoaderManagerBinderService mBinderService;
private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
public DataLoaderManagerService(Context context) {
super(context);
mContext = context;
+
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+
+ mHandler = new Handler(mThread.getLooper());
+
mBinderService = new DataLoaderManagerBinderService();
}
@@ -62,7 +72,7 @@
final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub {
@Override
public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params,
- IDataLoaderStatusListener listener) {
+ long bindDelayMs, IDataLoaderStatusListener listener) {
synchronized (mServiceConnections) {
if (mServiceConnections.get(dataLoaderId) != null) {
return true;
@@ -76,19 +86,21 @@
}
// Binds to the specific data loader service.
- DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId,
- listener);
+ final DataLoaderServiceConnection connection = new DataLoaderServiceConnection(
+ dataLoaderId, listener);
- Intent intent = new Intent();
+ final Intent intent = new Intent();
intent.setComponent(dataLoaderComponent);
- if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
- UserHandle.of(UserHandle.getCallingUserId()))) {
- Slog.e(TAG,
- "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId);
- mContext.unbindService(connection);
- return false;
- }
- return true;
+
+ return mHandler.postDelayed(() -> {
+ if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ mHandler, UserHandle.of(UserHandle.getCallingUserId()))) {
+ Slog.e(TAG,
+ "Failed to bind to: " + dataLoaderComponent + " for ID="
+ + dataLoaderId);
+ mContext.unbindService(connection);
+ }
+ }, bindDelayMs);
}
/**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f9ae9b8..b06e84d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -929,11 +930,23 @@
// Flag for bubble to make behaviour match documentLaunchMode=always.
intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true);
}
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0].setSourceBounds(sourceBounds);
+ // Replace theme for splash screen
+ final int splashScreenThemeResId =
+ mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(),
+ callingPackage, packageName, shortcutId, targetUserId);
+ if (splashScreenThemeResId != 0) {
+ if (startActivityOptions == null) {
+ startActivityOptions = new Bundle();
+ }
+ startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId);
+ }
+
return startShortcutIntentsAsPublisher(
intents, packageName, featureId, startActivityOptions, targetUserId);
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
boolean generateCompactDex = true;
switch (compilationReason) {
case PackageManagerService.REASON_FIRST_BOOT:
- case PackageManagerService.REASON_BOOT:
+ case PackageManagerService.REASON_BOOT_AFTER_OTA:
+ case PackageManagerService.REASON_POST_BOOT:
case PackageManagerService.REASON_INSTALL:
generateCompactDex = false;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2d393c0..2812830 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -295,23 +295,29 @@
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.isStaged()) {
- stagedSessionsToRestore.add(session.mStagedSession);
+ if (!session.isStaged()) {
+ continue;
+ }
+ StagingManager.StagedSession stagedSession = session.mStagedSession;
+ if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
+ && getSession(stagedSession.getParentSessionId()) == null) {
+ stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "An orphan staged session " + stagedSession.sessionId() + " is found, "
+ + "parent " + stagedSession.getParentSessionId() + " is missing");
+ continue;
+ }
+ if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted()
+ && !stagedSession.isInTerminalState()) {
+ // StagingManager.restoreSessions expects a list of committed, non-finalized
+ // parent staged sessions.
+ stagedSessionsToRestore.add(stagedSession);
}
}
}
- // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
+ // Don't hold mSessions lock when calling restoreSessions, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
- boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
- for (StagingManager.StagedSession session : stagedSessionsToRestore) {
- if (!session.isInTerminalState() && session.hasParentSessionId()
- && getSession(session.getParentSessionId()) == null) {
- session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "An orphan staged session " + session.sessionId() + " is found, "
- + "parent " + session.getParentSessionId() + " is missing");
- }
- mStagingManager.restoreSession(session, isDeviceUpgrading);
- }
+ // Note: restoreSessions mutates content of stagedSessionsToRestore.
+ mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading());
}
@GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c42569..0ce2673 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3778,7 +3778,9 @@
}
}
- if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
+ final long bindDelayMs = 0;
+ if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs,
+ statusListener)) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failed to initialize data loader");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4db6550..fc18ddb 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;
/**
@@ -780,17 +780,18 @@
// Compilation reasons.
public static final int REASON_UNKNOWN = -1;
public static final int REASON_FIRST_BOOT = 0;
- public static final int REASON_BOOT = 1;
- public static final int REASON_INSTALL = 2;
- public static final int REASON_INSTALL_FAST = 3;
- public static final int REASON_INSTALL_BULK = 4;
- public static final int REASON_INSTALL_BULK_SECONDARY = 5;
- public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
- public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
- public static final int REASON_BACKGROUND_DEXOPT = 8;
- public static final int REASON_AB_OTA = 9;
- public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
- public static final int REASON_SHARED = 11;
+ public static final int REASON_BOOT_AFTER_OTA = 1;
+ public static final int REASON_POST_BOOT = 2;
+ public static final int REASON_INSTALL = 3;
+ public static final int REASON_INSTALL_FAST = 4;
+ public static final int REASON_INSTALL_BULK = 5;
+ public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+ public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+ public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+ public static final int REASON_BACKGROUND_DEXOPT = 9;
+ public static final int REASON_AB_OTA = 10;
+ public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+ public static final int REASON_SHARED = 12;
public static final int REASON_LAST = REASON_SHARED;
@@ -2585,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) {
@@ -2675,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);
}
@@ -2779,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;
@@ -3025,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;
@@ -3952,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");
@@ -9368,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;
}
}
@@ -9419,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.
*/
@@ -9861,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,
@@ -11637,10 +11646,7 @@
// first boot, as they do not have profile data.
boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
- // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
- boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
- if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+ if (!causeUpgrade && !causeFirstBoot) {
return;
}
@@ -11657,7 +11663,7 @@
final long startTime = System.nanoTime();
final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+ causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
false /* bootComplete */);
final int elapsedTimeSeconds =
@@ -26213,30 +26219,7 @@
@Override
public void setKeepUninstalledPackages(final List<String> packageList) {
- Preconditions.checkNotNull(packageList);
- List<String> removedFromList = null;
- synchronized (mLock) {
- if (mKeepUninstalledPackages != null) {
- final int packagesCount = mKeepUninstalledPackages.size();
- for (int i = 0; i < packagesCount; i++) {
- String oldPackage = mKeepUninstalledPackages.get(i);
- if (packageList != null && packageList.contains(oldPackage)) {
- continue;
- }
- if (removedFromList == null) {
- removedFromList = new ArrayList<>();
- }
- removedFromList.add(oldPackage);
- }
- }
- mKeepUninstalledPackages = new ArrayList<>(packageList);
- if (removedFromList != null) {
- final int removedCount = removedFromList.size();
- for (int i = 0; i < removedCount; i++) {
- deletePackageIfUnusedLPr(removedFromList.get(i));
- }
- }
- }
+ PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList);
}
@Override
@@ -27734,6 +27717,43 @@
public DomainVerificationService.Connection getDomainVerificationConnection() {
return mDomainVerificationConnection;
}
+
+ @Override
+ public void setKeepUninstalledPackages(List<String> packageList) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.KEEP_UNINSTALLED_PACKAGES,
+ "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission");
+ Objects.requireNonNull(packageList);
+
+ setKeepUninstalledPackagesInternal(packageList);
+ }
+
+ private void setKeepUninstalledPackagesInternal(List<String> packageList) {
+ Preconditions.checkNotNull(packageList);
+ List<String> removedFromList = null;
+ synchronized (mLock) {
+ if (mKeepUninstalledPackages != null) {
+ final int packagesCount = mKeepUninstalledPackages.size();
+ for (int i = 0; i < packagesCount; i++) {
+ String oldPackage = mKeepUninstalledPackages.get(i);
+ if (packageList != null && packageList.contains(oldPackage)) {
+ continue;
+ }
+ if (removedFromList == null) {
+ removedFromList = new ArrayList<>();
+ }
+ removedFromList.add(oldPackage);
+ }
+ }
+ mKeepUninstalledPackages = new ArrayList<>(packageList);
+ if (removedFromList != null) {
+ final int removedCount = removedFromList.size();
+ for (int i = 0; i < removedCount; i++) {
+ deletePackageIfUnusedLPr(removedFromList.get(i));
+ }
+ }
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
// Names for compilation reasons.
public static final String REASON_STRINGS[] = {
"first-boot",
- "boot",
+ "boot-after-ota",
+ "post-boot",
"install",
"install-fast",
"install-bulk",
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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 9b092c0..bb4ec16 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -112,6 +112,7 @@
private static final String ATTR_BITMAP_PATH = "bitmap-path";
private static final String ATTR_ICON_URI = "icon-uri";
private static final String ATTR_LOCUS_ID = "locus-id";
+ private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id";
private static final String ATTR_PERSON_NAME = "name";
private static final String ATTR_PERSON_URI = "uri";
@@ -1654,6 +1655,7 @@
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
+ ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId());
ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
@@ -1861,6 +1863,7 @@
String bitmapPath;
String iconUri;
final String locusIdString;
+ int splashScreenThemeResId;
int backupVersionCode;
ArraySet<String> categories = null;
ArrayList<Person> persons = new ArrayList<>();
@@ -1871,6 +1874,8 @@
title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
+ splashScreenThemeResId = ShortcutService.parseIntAttribute(parser,
+ ATTR_SPLASH_SCREEN_THEME_ID);
text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
@@ -1964,7 +1969,8 @@
intents.toArray(new Intent[intents.size()]),
rank, extras, lastChangedTimestamp, flags,
iconResId, iconResName, bitmapPath, iconUri,
- disabledReason, persons.toArray(new Person[persons.size()]), locusId);
+ disabledReason, persons.toArray(new Person[persons.size()]), locusId,
+ splashScreenThemeResId);
}
private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index d3aace1..c06f01a 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -383,6 +383,8 @@
final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
final int disabledMessageResId = sa.getResourceId(
R.styleable.Shortcut_shortcutDisabledMessage, 0);
+ final int splashScreenTheme = sa.getResourceId(
+ R.styleable.Shortcut_splashScreenTheme, 0);
if (TextUtils.isEmpty(id)) {
Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
@@ -404,7 +406,8 @@
disabledMessageResId,
rank,
iconResId,
- enabled);
+ enabled,
+ splashScreenTheme);
} finally {
sa.recycle();
}
@@ -413,7 +416,7 @@
private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
int titleResId, int textResId, int disabledMessageResId,
- int rank, int iconResId, boolean enabled) {
+ int rank, int iconResId, boolean enabled, int splashScreenTheme) {
final int flags =
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -452,7 +455,8 @@
null, // icon Url
disabledReason,
null /* persons */,
- null /* locusId */);
+ null /* locusId */,
+ splashScreenTheme);
}
private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 863e3fe5..4d8abea 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3214,6 +3214,32 @@
}
@Override
+ public int getShortcutStartingThemeResId(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ Objects.requireNonNull(callingPackage, "callingPackage");
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(shortcutId, "shortcutId");
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+ throwIfUserLockedL(launcherUserId);
+
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave();
+
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return 0;
+ }
+
+ final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
+ return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0;
+ }
+ }
+
+ @Override
public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull String shortcutId, int userId) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 545567c..0a74032 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -50,8 +50,6 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IntArray;
@@ -63,6 +61,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
@@ -143,10 +142,16 @@
}
StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
+ this(context, packageParserSupplier, ApexManager.getInstance());
+ }
+
+ @VisibleForTesting
+ StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
+ ApexManager apexManager) {
mContext = context;
mPackageParserSupplier = packageParserSupplier;
- mApexManager = ApexManager.getInstance();
+ mApexManager = apexManager;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mPreRebootVerificationHandler = new PreRebootVerificationHandler(
BackgroundThread.get().getLooper());
@@ -354,11 +359,11 @@
}
// Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
- private void abortCheckpoint(int sessionId, String errorMsg) {
- String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
+ private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
+ boolean needsCheckpoint) {
Slog.e(TAG, failureReason);
try {
- if (supportsCheckpoint() && needsCheckpoint()) {
+ if (supportsCheckpoint && needsCheckpoint) {
// Store failure reason for next reboot
try (BufferedWriter writer =
new BufferedWriter(new FileWriter(mFailureReasonFile))) {
@@ -371,8 +376,9 @@
if (mApexManager.isApexSupported()) {
mApexManager.revertActiveSessions();
}
+
PackageHelper.getStorageManager().abortChanges(
- "StagingManager initiated", false /*retry*/);
+ "abort-staged-install", false /*retry*/);
}
} catch (Exception e) {
Slog.wtf(TAG, "Failed to abort checkpoint", e);
@@ -384,14 +390,6 @@
}
}
- private boolean supportsCheckpoint() throws RemoteException {
- return PackageHelper.getStorageManager().supportsCheckpoint();
- }
-
- private boolean needsCheckpoint() throws RemoteException {
- return PackageHelper.getStorageManager().needsCheckpoint();
- }
-
/**
* Utility function for extracting apex sessions out of multi-package/single session.
*/
@@ -517,96 +515,31 @@
}
}
- private void resumeSession(@NonNull StagedSession session)
- throws PackageManagerException {
+ private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
+ boolean needsCheckpoint) throws PackageManagerException {
Slog.d(TAG, "Resuming session " + session.sessionId());
final boolean hasApex = session.containsApexSession();
- ApexSessionInfo apexSessionInfo = null;
- if (hasApex) {
- // Check with apexservice whether the apex packages have been activated.
- apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId());
-
- // Prepare for logging a native crash during boot, if one occurred.
- if (apexSessionInfo != null && !TextUtils.isEmpty(
- apexSessionInfo.crashingNativeProcess)) {
- prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
- }
-
- if (apexSessionInfo != null && apexSessionInfo.isVerified) {
- // Session has been previously submitted to apexd, but didn't complete all the
- // pre-reboot verification, perhaps because the device rebooted in the meantime.
- // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
- // failed when not in checkpoint mode, hence it is being processed separately.
- Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to "
- + "be verified, resuming pre-reboot verification");
- mPreRebootVerificationHandler.startPreRebootVerification(session);
- return;
- }
- }
// Before we resume session, we check if revert is needed or not. Typically, we enter file-
// system checkpoint mode when we reboot first time in order to install staged sessions. We
// want to install staged sessions in this mode as rebooting now will revert user data. If
// something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
// have no effect on user data, so mark the sessions as failed instead.
- try {
- // If checkpoint is supported, then we only resume sessions if we are in checkpointing
- // mode. If not, we fail all sessions.
- if (supportsCheckpoint() && !needsCheckpoint()) {
- String revertMsg = "Reverting back to safe state. Marking "
- + session.sessionId() + " as failed.";
- final String reasonForRevert = getReasonForRevert();
- if (!TextUtils.isEmpty(reasonForRevert)) {
- revertMsg += " Reason for revert: " + reasonForRevert;
- }
- Slog.d(TAG, revertMsg);
- session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
- return;
+ // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
+ // If not, we fail all sessions.
+ if (supportsCheckpoint && !needsCheckpoint) {
+ String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
+ + " as failed.";
+ final String reasonForRevert = getReasonForRevert();
+ if (!TextUtils.isEmpty(reasonForRevert)) {
+ revertMsg += " Reason for revert: " + reasonForRevert;
}
- } catch (RemoteException e) {
- // Cannot continue staged install without knowing if fs-checkpoint is supported
- Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
- + session.sessionId(), e);
- // TODO: Mark all staged sessions together and reboot only once
- session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
- "Checkpoint support unknown. Aborting staged install.");
- if (hasApex) {
- mApexManager.revertActiveSessions();
- }
- mPowerManager.reboot("Checkpoint support unknown");
+ Slog.d(TAG, revertMsg);
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
return;
}
- // Check if apex packages in the session failed to activate
- if (hasApex) {
- if (apexSessionInfo == null) {
- final String errorMsg = "apexd did not know anything about a staged session "
- + "supposed to be activated";
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- if (isApexSessionFailed(apexSessionInfo)) {
- String errorMsg = "APEX activation failed. Check logcat messages from apexd "
- + "for more information.";
- if (!TextUtils.isEmpty(mNativeFailureReason)) {
- errorMsg = "Session reverted due to crashing native process: "
- + mNativeFailureReason;
- }
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
- // Apexd did not apply the session for some unknown reason. There is no
- // guarantee that apexd will install it next time. Safer to proactively mark
- // it as failed.
- final String errorMsg = "Staged session " + session.sessionId() + "at boot "
- + "didn't activate nor fail. Marking it as failed anyway.";
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- }
-
// Handle apk and apk-in-apex installation
if (hasApex) {
checkInstallationOfApkInApexSuccessful(session);
@@ -622,28 +555,24 @@
Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
session.setSessionApplied();
if (hasApex) {
- try {
- if (supportsCheckpoint()) {
- // Store the session ID, which will be marked as successful by ApexManager
- // upon boot completion.
- synchronized (mSuccessfulStagedSessionIds) {
- mSuccessfulStagedSessionIds.add(session.sessionId());
- }
- } else {
- // Mark sessions as successful immediately on non-checkpointing devices.
- mApexManager.markStagedSessionSuccessful(session.sessionId());
+ if (supportsCheckpoint) {
+ // Store the session ID, which will be marked as successful by ApexManager upon
+ // boot completion.
+ synchronized (mSuccessfulStagedSessionIds) {
+ mSuccessfulStagedSessionIds.add(session.sessionId());
}
- } catch (RemoteException e) {
- Slog.w(TAG, "Checkpoint support unknown, marking session as successful "
- + "immediately.");
+ } else {
+ // Mark sessions as successful immediately on non-checkpointing devices.
mApexManager.markStagedSessionSuccessful(session.sessionId());
}
}
}
- void onInstallationFailure(StagedSession session, PackageManagerException e) {
+ void onInstallationFailure(StagedSession session, PackageManagerException e,
+ boolean supportsCheckpoint, boolean needsCheckpoint) {
session.setSessionFailed(e.error, e.getMessage());
- abortCheckpoint(session.sessionId(), e.getMessage());
+ abortCheckpoint("Failed to install sessionId: " + session.sessionId()
+ + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
// If checkpoint is not supported, we have to handle failure for one staged session.
if (!session.containsApexSession()) {
@@ -767,8 +696,13 @@
"Cannot stage session " + session.sessionId() + " with package name null");
}
- boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
- Context.STORAGE_SERVICE)).isCheckpointSupported();
+ boolean supportsCheckpoint;
+ try {
+ supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+ } catch (RemoteException e) {
+ throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+ "Can't query fs-checkpoint status : " + e);
+ }
final boolean isRollback = isRollback(session);
@@ -911,60 +845,166 @@
|| apexSessionInfo.isRevertFailed;
}
- void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) {
- if (session.hasParentSessionId()) {
- // Only parent sessions can be restored
- return;
+ private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
+ int j = sessions.size();
+ for (int i = 0; i < j; ) {
+ // Maintain following invariant:
+ // * elements at positions [0, i) should be kept
+ // * elements at positions [j, n) should be remove.
+ // * n = sessions.size()
+ StagedSession session = sessions.get(i);
+ if (session.isDestroyed()) {
+ // Device rebooted before abandoned session was cleaned up.
+ session.abandon();
+ StagedSession session2 = sessions.set(j - 1, session);
+ sessions.set(i, session2);
+ j--;
+ } else if (!session.isSessionReady()) {
+ // The framework got restarted before the pre-reboot verification could complete,
+ // restart the verification.
+ mPreRebootVerificationHandler.startPreRebootVerification(session);
+ StagedSession session2 = sessions.set(j - 1, session);
+ sessions.set(i, session2);
+ j--;
+ } else {
+ i++;
+ }
}
- // Store this parent session which will be used to check overlapping later
- createSession(session);
- // The preconditions used during pre-reboot verification might have changed when device
- // is upgrading. Updated staged sessions to activation failed before we resume the session.
- StagedSession sessionToResume = session;
- if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) {
- sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Build fingerprint has changed");
- return;
- }
- checkStateAndResume(sessionToResume);
+ // Delete last j elements.
+ sessions.subList(j, sessions.size()).clear();
}
- private void checkStateAndResume(@NonNull StagedSession session) {
- // Do not resume session if boot completed already
+ void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
+ // Do not resume sessions if boot completed already
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
return;
}
- if (!session.isCommitted()) {
- // Session hasn't been committed yet, ignore.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ // Quick check that PackageInstallerService gave us sessions we expected.
+ Preconditions.checkArgument(!session.hasParentSessionId(),
+ session.sessionId() + " is a child session");
+ Preconditions.checkArgument(session.isCommitted(),
+ session.sessionId() + " is not committed");
+ Preconditions.checkArgument(!session.isInTerminalState(),
+ session.sessionId() + " is in terminal state");
+ // Store this parent session which will be used to check overlapping later
+ createSession(session);
+ }
+
+ if (isDeviceUpgrading) {
+ // TODO(ioffe): check that corresponding apex sessions are failed.
+ // The preconditions used during pre-reboot verification might have changed when device
+ // is upgrading. Fail all the sessions and exit early.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Build fingerprint has changed");
+ }
return;
}
- // Check the state of the session and decide what to do next.
- if (session.isSessionFailed() || session.isSessionApplied()) {
- // Final states, nothing to do.
+
+ boolean needsCheckpoint = false;
+ boolean supportsCheckpoint = false;
+ try {
+ supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+ needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+ } catch (RemoteException e) {
+ // This means that vold has crashed, and device is in a bad state.
+ throw new IllegalStateException("Failed to get checkpoint status", e);
+ }
+
+ if (sessions.size() > 1 && !supportsCheckpoint) {
+ throw new IllegalStateException("Detected multiple staged sessions on a device without "
+ + "fs-checkpoint support");
+ }
+
+ // Do a set of quick checks before resuming individual sessions:
+ // 1. Schedule a pre-reboot verification for non-ready sessions.
+ // 2. Abandon destroyed sessions.
+ handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
+
+ // 3. Check state of apex sessions is consistent. All non-applied sessions will be marked
+ // as failed.
+ final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
+ boolean hasFailedApexSession = false;
+ boolean hasAppliedApexSession = false;
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ if (!session.containsApexSession()) {
+ // At this point we are only interested in apex sessions.
+ continue;
+ }
+ final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
+ if (apexSession == null || apexSession.isUnknown) {
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
+ + "not know anything about a staged session supposed to be activated");
+ continue;
+ } else if (isApexSessionFailed(apexSession)) {
+ hasFailedApexSession = true;
+ String errorMsg = "APEX activation failed. Check logcat messages from apexd "
+ + "for more information.";
+ if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
+ prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
+ errorMsg = "Session reverted due to crashing native process: "
+ + apexSession.crashingNativeProcess;
+ }
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
+ continue;
+ } else if (apexSession.isActivated || apexSession.isSuccess) {
+ hasAppliedApexSession = true;
+ continue;
+ } else if (apexSession.isStaged) {
+ // Apexd did not apply the session for some unknown reason. There is no guarantee
+ // that apexd will install it next time. Safer to proactively mark it as failed.
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Staged session " + session.sessionId() + " at boot didn't activate nor "
+ + "fail. Marking it as failed anyway.");
+ } else {
+ Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Impossible state");
+ }
+ }
+
+ if (hasAppliedApexSession && hasFailedApexSession) {
+ abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
+ needsCheckpoint);
return;
}
- if (session.isDestroyed()) {
- // Device rebooted before abandoned session was cleaned up.
- session.abandon();
+
+ if (hasFailedApexSession) {
+ // Either of those means that we failed at least one apex session, hence we should fail
+ // all other sessions.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ if (session.isSessionFailed()) {
+ // Session has been already failed in the loop above.
+ continue;
+ }
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Another apex session failed");
+ }
return;
}
- if (!session.isSessionReady()) {
- // The framework got restarted before the pre-reboot verification could complete,
- // restart the verification.
- mPreRebootVerificationHandler.startPreRebootVerification(session);
- } else {
- // Session had already being marked ready. Start the checks to verify if there is any
- // follow-up work.
+
+ // Time to resume sessions.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
try {
- resumeSession(session);
+ resumeSession(session, supportsCheckpoint, needsCheckpoint);
} catch (PackageManagerException e) {
- onInstallationFailure(session, e);
+ onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
} catch (Exception e) {
Slog.e(TAG, "Staged install failed due to unhandled exception", e);
onInstallationFailure(session, new PackageManagerException(
SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Staged install failed due to unhandled exception: " + e));
+ "Staged install failed due to unhandled exception: " + e),
+ supportsCheckpoint, needsCheckpoint);
}
}
}
@@ -992,9 +1032,7 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
- mPreRebootVerificationHandler.readyToStart();
- BackgroundThread.getExecutor().execute(
- () -> logFailedApexSessionsIfNecessary());
+ onBootCompletedBroadcastReceived();
ctx.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
@@ -1002,6 +1040,12 @@
mFailureReasonFile.delete();
}
+ @VisibleForTesting
+ void onBootCompletedBroadcastReceived() {
+ mPreRebootVerificationHandler.readyToStart();
+ BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
+ }
+
private static class LocalIntentReceiverSync {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
@@ -1286,9 +1330,8 @@
private void handlePreRebootVerification_End(@NonNull StagedSession session) {
// Before marking the session as ready, start checkpoint service if available
try {
- IStorageManager storageManager = PackageHelper.getStorageManager();
- if (storageManager.supportsCheckpoint()) {
- storageManager.startCheckpoint(2);
+ if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+ PackageHelper.getStorageManager().startCheckpoint(2);
}
} catch (Exception e) {
// Failed to get hold of StorageManager
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
private static final int TRON_COMPILATION_REASON_ERROR = 0;
private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
- private static final int TRON_COMPILATION_REASON_BOOT = 3;
+ private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
private static final int TRON_COMPILATION_REASON_INSTALL = 4;
private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
private static final int
TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+ private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+ private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -618,7 +620,8 @@
case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
case "error" : return TRON_COMPILATION_REASON_ERROR;
case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
- case "boot" : return TRON_COMPILATION_REASON_BOOT;
+ case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+ case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
case "install" : return TRON_COMPILATION_REASON_INSTALL;
case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
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/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 0e88862..e05ef48 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,3 @@
-moltmann@google.com
zhanghai@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
@@ -7,5 +6,4 @@
per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
per-file DefaultPermissionGrantPolicy.java = patb@google.com
per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
-per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 30c334d..32bee58 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -38,6 +38,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Objects;
+import java.util.Set;
/**
* Permission definition.
@@ -345,6 +346,14 @@
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
}
+ public boolean isKnownSigner() {
+ return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
+ }
+
+ public Set<String> getKnownCerts() {
+ return mPermissionInfo.knownCerts;
+ }
+
public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
if (!oldPackageName.equals(mPermissionInfo.packageName)) {
return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index aff87111..71e53d9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2590,8 +2590,10 @@
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
+ ArraySet<String> isPrivilegedPermissionAllowlisted = null;
ArraySet<String> shouldGrantSignaturePermission = null;
ArraySet<String> shouldGrantInternalPermission = null;
+ ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
final List<String> requestedPermissions = pkg.getRequestedPermissions();
final int requestedPermissionsSize = requestedPermissions.size();
for (int i = 0; i < requestedPermissionsSize; i++) {
@@ -2604,15 +2606,24 @@
if (permission == null) {
continue;
}
- if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission)
- || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
+ if (permission.isPrivileged()
+ && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+ if (isPrivilegedPermissionAllowlisted == null) {
+ isPrivilegedPermissionAllowlisted = new ArraySet<>();
+ }
+ isPrivilegedPermissionAllowlisted.add(permissionName);
+ }
+ if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
+ || shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+ shouldGrantPrivilegedPermissionIfWasGranted))) {
if (shouldGrantSignaturePermission == null) {
shouldGrantSignaturePermission = new ArraySet<>();
}
shouldGrantSignaturePermission.add(permissionName);
}
if (permission.isInternal()
- && shouldGrantPermissionByProtectionFlags(pkg, ps, permission)) {
+ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+ shouldGrantPrivilegedPermissionIfWasGranted)) {
if (shouldGrantInternalPermission == null) {
shouldGrantInternalPermission = new ArraySet<>();
}
@@ -2830,14 +2841,22 @@
if ((bp.isNormal() && shouldGrantNormalPermission)
|| (bp.isSignature()
- && ((shouldGrantSignaturePermission != null
- && shouldGrantSignaturePermission.contains(permName))
- || ((bp.isDevelopment() || bp.isRole())
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantSignaturePermission,
+ permName)
+ || (((bp.isPrivileged() && CollectionUtils.contains(
+ shouldGrantPrivilegedPermissionIfWasGranted,
+ permName)) || bp.isDevelopment() || bp.isRole())
&& origState.isPermissionGranted(permName))))
|| (bp.isInternal()
- && ((shouldGrantInternalPermission != null
- && shouldGrantInternalPermission.contains(permName))
- || ((bp.isDevelopment() || bp.isRole())
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantInternalPermission,
+ permName)
+ || (((bp.isPrivileged() && CollectionUtils.contains(
+ shouldGrantPrivilegedPermissionIfWasGranted,
+ permName)) || bp.isDevelopment() || bp.isRole())
&& origState.isPermissionGranted(permName))))) {
// Grant an install permission.
if (uidState.grantPermission(bp)) {
@@ -3343,7 +3362,100 @@
return allowed;
}
- private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+ private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+ @NonNull PackageSetting packageSetting, @NonNull Permission permission) {
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+ return true;
+ }
+ final String packageName = pkg.getPackageName();
+ if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+ return true;
+ }
+ if (!pkg.isPrivileged()) {
+ return true;
+ }
+ if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) {
+ return true;
+ }
+ final String permissionName = permission.getName();
+ if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+ return true;
+ }
+ if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+ return false;
+ }
+ // Updated system apps do not need to be allowlisted
+ if (packageSetting.getPkgState().isUpdatedSystemApp()) {
+ // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
+ // can be granted, because an updated system app may be in a shared UID, and in case a
+ // new privileged permission is requested by the updated system app but not the factory
+ // app, although this app and permission combination isn't in the allowlist and can't
+ // get the permission this way, other apps in the shared UID may still get it. A proper
+ // fix for this would be to perform the reconciliation by UID, but for now let's keep
+ // the old workaround working, which is to keep granted privileged permissions still
+ // granted.
+ return true;
+ }
+ // Only enforce the allowlist on boot
+ if (!mSystemReady) {
+ final ApexManager apexManager = ApexManager.getInstance();
+ final String containingApexPackageName =
+ apexManager.getActiveApexPackageNameContainingPackage(packageName);
+ final boolean isInUpdatedApex = containingApexPackageName != null
+ && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+ MATCH_ACTIVE_PACKAGE));
+ // Apps that are in updated apexs' do not need to be allowlisted
+ if (!isInUpdatedApex) {
+ Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+ + packageName + " (" + pkg.getPath()
+ + ") not in privapp-permissions allowlist");
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+ synchronized (mLock) {
+ if (mPrivappPermissionsViolations == null) {
+ mPrivappPermissionsViolations = new ArraySet<>();
+ }
+ mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+ + permissionName);
+ }
+ }
+ }
+ }
+ return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+ }
+
+ private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+ }
+ return CollectionUtils.contains(permissions, permission);
+ }
+
+ private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+ }
+ return CollectionUtils.contains(permissions, permission);
+ }
+
+ private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
@NonNull Permission bp) {
// expect single system package
String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
@@ -3371,10 +3483,10 @@
}
private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
- @NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
+ @NonNull PackageSetting pkgSetting, @NonNull Permission bp,
+ @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) {
boolean allowed = false;
- final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
- final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+ final boolean isPrivilegedPermission = bp.isPrivileged();
final boolean isOemPermission = bp.isOem();
if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
final String permissionName = bp.getName();
@@ -3384,21 +3496,27 @@
final PackageSetting disabledPs = mPackageManagerInt
.getDisabledSystemPackage(pkg.getPackageName());
final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
- if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
- permissionName)) {
- allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
- true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
- permissionName));
+ if (disabledPkg != null
+ && ((isPrivilegedPermission && disabledPkg.isPrivileged())
+ || (isOemPermission && canGrantOemPermission(disabledPkg,
+ permissionName)))) {
+ if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
+ allowed = true;
+ } else {
+ // If the original was granted this permission, we take
+ // that grant decision as read and propagate it to the
+ // update.
+ shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName);
+ }
}
} else {
- allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+ allowed = (isPrivilegedPermission && pkg.isPrivileged())
|| (isOemPermission && canGrantOemPermission(pkg, permissionName));
}
// In any case, don't grant a privileged permission to privileged vendor apps, if
// the permission's protectionLevel does not have the extra 'vendorPrivileged'
// flag.
- if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission
- && pkg.isVendor()) {
+ if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
Slog.w(TAG, "Permission " + permissionName
+ " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+ " because it isn't a 'vendorPrivileged' permission.");
@@ -3438,6 +3556,11 @@
// Any pre-installed system app is allowed to get this permission.
allowed = true;
}
+ if (!allowed && bp.isKnownSigner()) {
+ // If the permission is to be granted to a known signer then check if any of this
+ // app's signing certificates are in the trusted certificate digest Set.
+ allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+ }
// Deferred to be checked under permission data lock inside restorePermissionState().
//if (!allowed && bp.isDevelopment()) {
// // For development permissions, a development permission
@@ -3536,90 +3659,6 @@
return mPackageManagerInt.getPackageSetting(sourcePackageName);
}
- private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
- boolean isUpdatedSystemApp, @NonNull Permission permission) {
- if (!pkg.isPrivileged()) {
- return false;
- }
- final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
- permission.getPackageName());
- if (!isPlatformPermission) {
- return true;
- }
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
- return true;
- }
- final String permissionName = permission.getName();
- if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
- return true;
- }
- // Only enforce the allowlist on boot
- if (!mSystemReady
- // Updated system apps do not need to be allowlisted
- && !isUpdatedSystemApp) {
- final ApexManager apexManager = ApexManager.getInstance();
- final String packageName = pkg.getPackageName();
- final String containingApexPackageName =
- apexManager.getActiveApexPackageNameContainingPackage(packageName);
- final boolean isInUpdatedApex = containingApexPackageName != null
- && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
- MATCH_ACTIVE_PACKAGE));
- // Apps that are in updated apexs' do not need to be allowlisted
- if (!isInUpdatedApex) {
- // it's only a reportable violation if the permission isn't explicitly
- // denied
- if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
- return false;
- }
- Slog.w(TAG, "Privileged permission " + permissionName + " for package "
- + packageName + " (" + pkg.getPath()
- + ") not in privapp-permissions allowlist");
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- synchronized (mLock) {
- if (mPrivappPermissionsViolations == null) {
- mPrivappPermissionsViolations = new ArraySet<>();
- }
- mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
- + permissionName);
- }
- }
- }
- }
- return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
- }
-
- private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
- @NonNull String permission) {
- final SystemConfig systemConfig = SystemConfig.getInstance();
- final Set<String> permissions;
- if (pkg.isVendor()) {
- permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
- } else {
- permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
- }
- return permissions != null && permissions.contains(permission);
- }
-
- private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
- @NonNull String permission) {
- final SystemConfig systemConfig = SystemConfig.getInstance();
- final Set<String> permissions;
- if (pkg.isVendor()) {
- permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
- } else {
- permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
- }
- return permissions != null && permissions.contains(permission);
- }
-
private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
if (!pkg.isOem()) {
return false;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index 36efb39..080de73 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -21,11 +21,8 @@
import android.compat.annotation.EnabledSince;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedIntentInfo;
-import android.os.Binder;
import android.os.Build;
import android.util.ArraySet;
import android.util.Patterns;
@@ -36,19 +33,31 @@
import java.util.List;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class DomainVerificationCollector {
+ // The default domain name matcher doesn't account for wildcards, so prefix with *.
+ private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
+ Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
+
@NonNull
private final PlatformCompat mPlatformCompat;
@NonNull
private final SystemConfig mSystemConfig;
+ @NonNull
+ private final Matcher mDomainMatcher;
+
public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
@NonNull SystemConfig systemConfig) {
mPlatformCompat = platformCompat;
mSystemConfig = systemConfig;
+
+ // Cache the matcher to avoid calling into native on each check
+ mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher("");
}
/**
@@ -144,7 +153,10 @@
if (intent.handlesWebUris(false)) {
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
- domains.add(intent.getDataAuthority(index).getHost());
+ String host = intent.getDataAuthority(index).getHost();
+ if (isValidHost(host)) {
+ domains.add(host);
+ }
}
}
}
@@ -188,13 +200,22 @@
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
String host = intent.getDataAuthority(index).getHost();
- // It's easy to misconfigure autoVerify intent filters, so to avoid
- // adding unintended hosts, check if the host is an HTTP domain.
- if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+ if (isValidHost(host)) {
domains.add(host);
}
}
}
}
}
+
+ /**
+ * It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts,
+ * check if the host is an HTTP domain. This applies for both legacy and modern versions of
+ * the API, which will strip invalid hosts from the legacy parsing result. This is done to
+ * improve the reliability of any legacy verifiers.
+ */
+ private boolean isValidHost(String host) {
+ mDomainMatcher.reset(host);
+ return mDomainMatcher.matches();
+ }
}
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/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 9389e63..a804065 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -30,7 +30,6 @@
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Process;
import android.os.UserHandle;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -45,6 +44,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;
@@ -168,22 +168,58 @@
return true;
}
- Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet());
- successfulDomains.removeAll(response.failedDomains);
+ AndroidPackage pkg = mConnection.getPackage(packageName);
+ if (pkg == null) {
+ return true;
+ }
+
+ ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains);
+ Map<String, Integer> hostToStateMap = info.getHostToStateMap();
+ Set<String> hostKeySet = hostToStateMap.keySet();
+ ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet);
+ successfulDomains.removeAll(failedDomains);
+
+ // v1 doesn't handle wildcard domains, so check them here for the verifier
+ int size = successfulDomains.size();
+ for (int index = size - 1; index >= 0; index--) {
+ String domain = successfulDomains.valueAt(index);
+ if (domain.startsWith("*.")) {
+ String nonWildcardDomain = domain.substring(2);
+ if (failedDomains.contains(nonWildcardDomain)) {
+ failedDomains.add(domain);
+ successfulDomains.removeAt(index);
+
+ // It's possible to declare a wildcard without declaring its
+ // non-wildcard equivalent, so if it wasn't originally declared,
+ // remove the transformed domain from the failed set. Otherwise the
+ // manager will not accept the failed set as it contains an undeclared
+ // domain.
+ if (!hostKeySet.contains(nonWildcardDomain)) {
+ failedDomains.remove(nonWildcardDomain);
+ }
+ }
+ }
+ }
int callingUid = response.callingUid;
- try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- successfulDomains, DomainVerificationState.STATE_SUCCESS);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException ignored) {
+ if (!successfulDomains.isEmpty()) {
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ successfulDomains, DomainVerificationState.STATE_SUCCESS);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
+ }
}
- try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- new ArraySet<>(response.failedDomains),
- DomainVerificationState.STATE_LEGACY_FAILURE);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException ignored) {
+
+ if (!failedDomains.isEmpty()) {
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
+ }
}
return true;
@@ -235,7 +271,21 @@
// The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
// not the version of the verification agent on device.
ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
- return TextUtils.join(" ", domains);
+
+ // v1 doesn't handle wildcard domains, so transform them here to the root
+ StringBuilder builder = new StringBuilder();
+ int size = domains.size();
+ for (int index = 0; index < size; index++) {
+ if (index > 0) {
+ builder.append(" ");
+ }
+ String domain = domains.valueAt(index);
+ if (domain.startsWith("*.")) {
+ domain = domain.substring(2);
+ }
+ builder.append(domain);
+ }
+ return builder.toString();
}
private static class Response {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index ac358db..4e1065a 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -17,6 +17,7 @@
package com.android.server.policy;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -84,7 +85,8 @@
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
@VisibleForTesting
- static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+ static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
+ "DEFAULT");
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc81961..6a441f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -346,8 +346,21 @@
/** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
- /** Amount of time (in milliseconds) a toast window can be shown. */
- public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+ /**
+ * Extra time for additional SystemUI animations.
+ * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs,
+ * {@link DisplayPolicy} ensures that the window manager removes toast windows after
+ * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for
+ * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum
+ * of 3.5 seconds and the animations are finished before window manager removes the window.
+ */
+ public static final int TOAST_WINDOW_ANIM_BUFFER = 600;
+
+ /**
+ * Amount of time (in milliseconds) a toast window can be shown before it's automatically
+ * removed by window manager.
+ */
+ public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
/**
* Lock protecting internal state. Must not call out into window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..c0b8202 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
import android.view.Display;
import android.view.KeyEvent;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -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/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ea41980..b7285d5 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -73,6 +73,8 @@
private TimerTrigger mTimerTrigger;
@Nullable
private StatsPullAtomCallbackImpl mPullAtomCallback;
+ @Nullable
+ private PowerStatsInternal mPowerStatsInternal;
@VisibleForTesting
static class Injector {
@@ -125,8 +127,8 @@
}
StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
- IPowerStatsHALWrapper powerStatsHALWrapper) {
- return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+ PowerStatsInternal powerStatsInternal) {
+ return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
}
}
@@ -175,21 +177,14 @@
@Override
public void onStart() {
if (getPowerStatsHal().isInitialized()) {
- // Only create internal service if PowerStatsHal is available.
- publishLocalService(PowerStatsInternal.class, new LocalService());
+ mPowerStatsInternal = new LocalService();
+ publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
}
publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
}
private void onSystemServicesReady() {
- if (getPowerStatsHal().isInitialized()) {
- if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
-
- // Only start statsd pullers if initialization is successful.
- mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
- } else {
- Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
- }
+ mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
}
private void onBootCompleted() {
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index 7c6999a..bdabefb 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -24,26 +24,31 @@
import android.hardware.power.stats.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
import android.util.StatsEvent;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
* SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
*/
public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+ private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName();
private Context mContext;
- private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+ private PowerStatsInternal mPowerStatsInternal;
private Map<Integer, Channel> mChannels = new HashMap();
private Map<Integer, String> mEntityNames = new HashMap();
- private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+ private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+ private static final int STATS_PULL_TIMEOUT_MILLIS = 2000;
+ private static final boolean DEBUG = false;
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
@@ -57,21 +62,28 @@
}
}
- private void initPullOnDevicePowerMeasurement() {
- Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
- if (channels == null) {
- return;
+ private boolean initPullOnDevicePowerMeasurement() {
+ Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo();
+ if (channels == null || channels.length == 0) {
+ Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller");
+ return false;
}
for (int i = 0; i < channels.length; i++) {
final Channel channel = channels[i];
mChannels.put(channel.id, channel);
}
+
+ return true;
}
private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
- EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
- if (energyMeasurements == null) {
+ final EnergyMeasurement[] energyMeasurements;
+ try {
+ energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0])
+ .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to readEnergyMeterAsync", e);
return StatsManager.PULL_SKIP;
}
@@ -91,10 +103,11 @@
return StatsManager.PULL_SUCCESS;
}
- private void initSubsystemSleepState() {
- PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
- if (entities == null) {
- return;
+ private boolean initSubsystemSleepState() {
+ PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+ if (entities == null || entities.length == 0) {
+ Slog.e(TAG, "Failed to init SubsystemSleepState puller");
+ return false;
}
for (int i = 0; i < entities.length; i++) {
@@ -108,13 +121,20 @@
mEntityNames.put(entity.id, entity.name);
mStateNames.put(entity.id, states);
}
+
+ return true;
}
private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
- StateResidencyResult[] results = mPowerStatsHALWrapper.getStateResidency(new int[0]);
- if (results == null) {
+ final StateResidencyResult[] results;
+ try {
+ results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+ .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to getStateResidencyAsync", e);
return StatsManager.PULL_SKIP;
}
+
for (int i = 0; i < results.length; i++) {
final StateResidencyResult result = results[i];
for (int j = 0; j < result.stateResidencyData.length; j++) {
@@ -131,22 +151,33 @@
return StatsManager.PULL_SUCCESS;
}
- public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+ public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) {
+ if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
mContext = context;
- mPowerStatsHALWrapper = powerStatsHALWrapper;
- initPullOnDevicePowerMeasurement();
- initSubsystemSleepState();
+ mPowerStatsInternal = powerStatsInternal;
+
+ if (powerStatsInternal == null) {
+ Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+ return;
+ }
StatsManager manager = mContext.getSystemService(StatsManager.class);
- manager.setPullAtomCallback(
- FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
- null, // use default PullAtomMetadata values
- ConcurrentUtils.DIRECT_EXECUTOR,
- this);
- manager.setPullAtomCallback(
- FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
- null, // use default PullAtomMetadata values
- ConcurrentUtils.DIRECT_EXECUTOR,
- this);
+
+ if (initPullOnDevicePowerMeasurement()) {
+ manager.setPullAtomCallback(
+ FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ this);
+ }
+
+ if (initSubsystemSleepState()) {
+ manager.setPullAtomCallback(
+ FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ this);
+ }
}
}
diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS
index b94d988..31e3549 100644
--- a/services/core/java/com/android/server/role/OWNERS
+++ b/services/core/java/com/android/server/role/OWNERS
@@ -1,5 +1,4 @@
svetoslavganov@google.com
-moltmann@google.com
zhanghai@google.com
evanseverson@google.com
eugenesusla@google.com
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 539b413..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})
@@ -1612,9 +1613,6 @@
// Aggregate times for the same uids.
SparseArray<long[]> aggregated = new SparseArray<>();
mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
- // For uids known to be aggregated from many entries allow mutating in place to avoid
- // many copies. Otherwise, copy before aggregating.
- boolean mutateInPlace = false;
if (UserHandle.isIsolated(uid)) {
// Skip individual isolated uids because they are recycled and quickly removed from
// the underlying data source.
@@ -1622,26 +1620,18 @@
} else if (UserHandle.isSharedAppGid(uid)) {
// All shared app gids are accounted together.
uid = LAST_SHARED_APPLICATION_GID;
- mutateInPlace = true;
} else {
// Everything else is accounted under their base uid.
uid = UserHandle.getAppId(uid);
}
long[] aggCpuFreqTimeMs = aggregated.get(uid);
- if (aggCpuFreqTimeMs != null) {
- if (!mutateInPlace) {
- aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
- aggregated.put(uid, aggCpuFreqTimeMs);
- }
- for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
- aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
- }
- } else {
- if (mutateInPlace) {
- cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
- }
- aggregated.put(uid, cpuFreqTimeMs);
+ if (aggCpuFreqTimeMs == null) {
+ aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+ aggregated.put(uid, aggCpuFreqTimeMs);
+ }
+ for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+ aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
}
});
@@ -1660,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) {
@@ -1729,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/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index eb4a050..0087c0c 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -158,6 +158,29 @@
}
/**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long getAnrDelayMillis(String packageName, int uid)
+ throws ExternalStorageServiceException {
+ synchronized (mLock) {
+ int size = mConnections.size();
+ for (int i = 0; i < size; i++) {
+ int key = mConnections.keyAt(i);
+ StorageUserConnection connection = mConnections.get(key);
+ if (connection != null) {
+ long delay = connection.getAnrDelayMillis(packageName, uid);
+ if (delay > 0) {
+ return delay;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
* Removes and returns the {@link StorageUserConnection} for {@code vol}.
*
* Does nothing if {@link #shouldHandle} is {@code false}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 13cceee..709d558 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -16,6 +16,7 @@
package com.android.server.storage;
+import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS;
import static android.service.storage.ExternalStorageService.EXTRA_ERROR;
import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE;
import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE;
@@ -143,6 +144,24 @@
}
/**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long getAnrDelayMillis(String packageName, int uid)
+ throws ExternalStorageServiceException {
+ synchronized (mSessionsLock) {
+ for (String sessionId : mSessions.keySet()) {
+ long delay = mActiveConnection.getAnrDelayMillis(packageName, uid);
+ if (delay > 0) {
+ return delay;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
* Removes a session without ending it or waiting for exit.
*
* This should only be used if the session has certainly been ended because the volume was
@@ -234,6 +253,9 @@
@GuardedBy("mLock")
private final ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>();
+ @GuardedBy("mLock")
+ private final ArrayList<CompletableFuture<Long>> mOutstandingTimeoutOps = new ArrayList<>();
+
@Override
public void close() {
ServiceConnection oldConnection = null;
@@ -250,6 +272,9 @@
for (CompletableFuture<Void> op : mOutstandingOps) {
op.cancel(true);
}
+ for (CompletableFuture<Long> op : mOutstandingTimeoutOps) {
+ op.cancel(true);
+ }
mOutstandingOps.clear();
}
@@ -264,27 +289,44 @@
}
}
- private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception {
- CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+ private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
CompletableFuture<Void> opFuture = new CompletableFuture<>();
+ RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
+
+ waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
+ DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ }
+
+ private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception {
+ CompletableFuture<Long> opFuture = new CompletableFuture<>();
+ RemoteCallback callback =
+ new RemoteCallback(result -> setTimeoutResult(result, opFuture));
+
+ return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps,
+ 1 /* timeoutSeconds */);
+ }
+
+ private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
+ CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
+ long timeoutSeconds) throws Exception {
+ CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
try {
synchronized (mLock) {
- mOutstandingOps.add(opFuture);
+ outstandingOps.add(opFuture);
}
- serviceFuture.thenCompose(service -> {
+ return serviceFuture.thenCompose(service -> {
try {
- asyncCall.run(service,
- new RemoteCallback(result -> setResult(result, opFuture)));
+ asyncCall.run(service, callback);
} catch (RemoteException e) {
opFuture.completeExceptionally(e);
}
return opFuture;
- }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }).get(timeoutSeconds, TimeUnit.SECONDS);
} finally {
synchronized (mLock) {
- mOutstandingOps.remove(opFuture);
+ outstandingOps.remove(opFuture);
}
}
}
@@ -292,9 +334,9 @@
public void startSession(Session session, ParcelFileDescriptor fd)
throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) -> service.startSession(session.sessionId,
+ waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId,
FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
- fd, session.upperPath, session.lowerPath, callback));
+ fd, session.upperPath, session.lowerPath, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to start session: " + session, e);
} finally {
@@ -308,7 +350,7 @@
public void endSession(Session session) throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.endSession(session.sessionId, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to end session: " + session, e);
@@ -319,7 +361,7 @@
public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws
ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.notifyVolumeStateChanged(sessionId, vol, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to notify volume state changed "
@@ -330,7 +372,7 @@
public void freeCache(String sessionId, String volumeUuid, long bytes)
throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.freeCache(sessionId, volumeUuid, bytes, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to free " + bytes
@@ -338,6 +380,27 @@
}
}
+ public long getAnrDelayMillis(String packgeName, int uid)
+ throws ExternalStorageServiceException {
+ try {
+ return waitForAsyncLong((service, callback) ->
+ service.getAnrDelayMillis(packgeName, uid, callback));
+ } catch (Exception e) {
+ throw new ExternalStorageServiceException("Failed to notify app not responding: "
+ + packgeName, e);
+ }
+ }
+
+ private void setTimeoutResult(Bundle result, CompletableFuture<Long> future) {
+ ParcelableException ex = result.getParcelable(EXTRA_ERROR);
+ if (ex != null) {
+ future.completeExceptionally(ex);
+ } else {
+ long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS);
+ future.complete(timeoutMs);
+ }
+ }
+
private void setResult(Bundle result, CompletableFuture<Void> future) {
ParcelableException ex = result.getParcelable(EXTRA_ERROR);
if (ex != null) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index bbbd19f..b210339 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 6a4c276..792f372 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 7cd4184..c4c620c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,7 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2503e81..0ed26db 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,15 +127,37 @@
* +----------------------------+
* </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 {
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+ @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 InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
@@ -137,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 {}
/**
@@ -384,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();
@@ -412,11 +462,26 @@
@NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
@NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
@NonNull private final Dependencies mDeps;
-
@NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
@NonNull private final IpSecManager mIpSecManager;
- @NonNull private final IpSecTunnelInterface mTunnelIface;
+
+ @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;
@@ -480,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,
@@ -517,6 +588,9 @@
mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
+ mWakeLock =
+ mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
mUnderlyingNetworkTracker =
mDeps.newUnderlyingNetworkTracker(
mVcnContext,
@@ -526,20 +600,6 @@
mUnderlyingNetworkTrackerCallback);
mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
- IpSecTunnelInterface iface;
- try {
- iface =
- mIpSecManager.createIpSecTunnelInterface(
- DUMMY_ADDR, DUMMY_ADDR, new Network(-1));
- } catch (IOException | ResourceUnavailableException e) {
- teardownAsynchronously();
- mTunnelIface = null;
-
- return;
- }
-
- mTunnelIface = iface;
-
addState(mDisconnectedState);
addState(mDisconnectingState);
addState(mConnectingState);
@@ -557,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));
@@ -573,6 +633,13 @@
mTunnelIface.close();
}
+ releaseWakeLock();
+
+ cancelTeardownTimeoutAlarm();
+ cancelDisconnectRequestAlarm();
+ cancelRetryTimeoutAlarm();
+ cancelSafemodeAlarm();
+
mUnderlyingNetworkTracker.teardown();
}
@@ -589,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 {
@@ -671,7 +983,7 @@
enterState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
@@ -682,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;
}
@@ -709,7 +1046,7 @@
exitState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
@@ -787,6 +1124,8 @@
if (mIkeSession != null || mNetworkAgent != null) {
Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
}
+
+ cancelSafemodeAlarm();
}
@Override
@@ -810,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);
}
@@ -861,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;
}
@@ -873,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
@@ -918,6 +1245,10 @@
transitionTo(mDisconnectedState);
}
break;
+ case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafemode();
+ mSafemodeTimeoutAlarm = null;
+ break;
default:
logUnhandledMessage(msg);
break;
@@ -927,6 +1258,8 @@
@Override
protected void exitState() throws Exception {
mSkipRetryTimeout = false;
+
+ cancelTeardownTimeoutAlarm();
}
}
@@ -998,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;
@@ -1041,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();
@@ -1049,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,
@@ -1117,8 +1470,17 @@
class ConnectedState extends ConnectedStateBase {
@Override
protected void enterState() throws Exception {
- // Successful connection, clear failed attempt counter
- mFailedAttempts = 0;
+ if (mTunnelIface == null) {
+ try {
+ // Requires a real Network object in order to be created; doing this any earlier
+ // means not having a real Network object, or picking an incorrect Network.
+ mTunnelIface =
+ mIpSecManager.createIpSecTunnelInterface(
+ DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
+ } catch (IOException | ResourceUnavailableException e) {
+ teardownAsynchronously();
+ }
+ }
}
@Override
@@ -1155,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;
@@ -1197,8 +1563,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();
+ }
}
/**
@@ -1216,8 +1593,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());
}
}
@@ -1230,8 +1607,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
@@ -1242,19 +1617,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();
@@ -1434,6 +1816,11 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
+ void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
+ mTunnelIface = tunnelIface;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
return mUnderlyingNetworkTrackerCallback;
}
@@ -1522,6 +1909,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();
+ }
}
/**
@@ -1601,4 +2008,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/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 3968723..685dce4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -21,16 +21,14 @@
import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorManager;
import android.util.SparseArray;
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
-/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
+/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
+final class InputDeviceDelegate implements InputManager.InputDeviceListener {
private static final String TAG = "InputDeviceDelegate";
private final Object mLock = new Object();
@@ -38,7 +36,7 @@
private final InputManager mInputManager;
@GuardedBy("mLock")
- private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();
+ private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>();
/**
* Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
@@ -47,7 +45,7 @@
@GuardedBy("mLock")
private boolean mShouldVibrateInputDevices;
- public InputDeviceDelegate(Context context, Handler handler) {
+ InputDeviceDelegate(Context context, Handler handler) {
mHandler = handler;
mInputManager = context.getSystemService(InputManager.class);
}
@@ -81,32 +79,22 @@
}
/**
- * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
+ * Vibrate all {@link InputDevice} with vibrators using given effect.
*
* @return {@link #isAvailable()}
*/
public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibrationEffect effect,
String reason, VibrationAttributes attrs) {
synchronized (mLock) {
- // TODO(b/159207608): Pass on the combined vibration once InputManager is merged
- if (effect instanceof CombinedVibrationEffect.Mono) {
- VibrationEffect e = ((CombinedVibrationEffect.Mono) effect).getEffect();
- if (e instanceof VibrationEffect.Prebaked) {
- VibrationEffect fallback = ((VibrationEffect.Prebaked) e).getFallbackEffect();
- if (fallback != null) {
- e = fallback;
- }
- }
- for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
- mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, e, reason, attrs);
- }
+ for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
+ mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
}
return mInputDeviceVibrators.size() > 0;
}
}
/**
- * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
+ * Cancel vibration on all {@link InputDevice} with vibrators.
*
* @return {@link #isAvailable()}
*/
@@ -147,9 +135,9 @@
if (device == null) {
continue;
}
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.put(device.getId(), vibrator);
+ VibratorManager vibratorManager = device.getVibratorManager();
+ if (vibratorManager.getVibratorIds().length > 0) {
+ mInputDeviceVibrators.put(device.getId(), vibratorManager);
}
}
} else {
@@ -171,9 +159,9 @@
mInputDeviceVibrators.remove(deviceId);
return;
}
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.put(deviceId, vibrator);
+ VibratorManager vibratorManager = device.getVibratorManager();
+ if (vibratorManager.getVibratorIds().length > 0) {
+ mInputDeviceVibrators.put(device.getId(), vibratorManager);
} else {
mInputDeviceVibrators.remove(deviceId);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e0f5408..607da3c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,25 +25,16 @@
import android.os.VibrationEffect;
import android.util.proto.ProtoOutputStream;
-import com.android.server.ComposedProto;
-import com.android.server.OneShotProto;
-import com.android.server.PrebakedProto;
-import com.android.server.VibrationAttributesProto;
-import com.android.server.VibrationEffectProto;
-import com.android.server.VibrationProto;
-import com.android.server.WaveformProto;
-
import java.text.SimpleDateFormat;
import java.util.Date;
/** Represents a vibration request to the vibrator service. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public class Vibration {
+final class Vibration {
private static final String TAG = "Vibration";
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
- public enum Status {
+ enum Status {
RUNNING,
FINISHED,
FORWARDED_TO_INPUT_DEVICES,
@@ -91,7 +82,7 @@
private long mEndTimeDebug;
private Status mStatus;
- public Vibration(IBinder token, int id, CombinedVibrationEffect effect,
+ Vibration(IBinder token, int id, CombinedVibrationEffect effect,
VibrationAttributes attrs, int uid, String opPkg, String reason) {
this.token = token;
this.mEffect = effect;
@@ -157,7 +148,7 @@
}
/** Debug information about vibrations. */
- public static final class DebugInfo {
+ static final class DebugInfo {
private final long mStartTimeDebug;
private final long mEndTimeDebug;
private final CombinedVibrationEffect mEffect;
@@ -169,7 +160,7 @@
private final String mReason;
private final Status mStatus;
- public DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
+ DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
CombinedVibrationEffect originalEffect, float scale, VibrationAttributes attrs,
int uid, String opPkg, String reason, Status status) {
mStartTimeDebug = startTimeDebug;
@@ -235,21 +226,49 @@
}
private void dumpEffect(
- ProtoOutputStream proto, long fieldId, CombinedVibrationEffect combinedEffect) {
- VibrationEffect effect;
- // TODO(b/177805090): add proper support for dumping combined effects to proto
- if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
- effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
- } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
- effect = ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects().valueAt(0);
- } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
- dumpEffect(proto, fieldId,
- ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects().get(0));
- return;
- } else {
- // Unknown combined effect, skip dump.
- return;
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect effect) {
+ dumpEffect(proto, fieldId,
+ (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+ .addNext(effect)
+ .combine());
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Sequential effect) {
+ final long token = proto.start(fieldId);
+ for (int i = 0; i < effect.getEffects().size(); i++) {
+ CombinedVibrationEffect nestedEffect = effect.getEffects().get(i);
+ if (nestedEffect instanceof CombinedVibrationEffect.Mono) {
+ dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+ (CombinedVibrationEffect.Mono) nestedEffect);
+ } else if (nestedEffect instanceof CombinedVibrationEffect.Stereo) {
+ dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+ (CombinedVibrationEffect.Stereo) nestedEffect);
+ }
+ proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
}
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Mono effect) {
+ final long token = proto.start(fieldId);
+ dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Stereo effect) {
+ final long token = proto.start(fieldId);
+ for (int i = 0; i < effect.getEffects().size(); i++) {
+ proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
+ dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
+ }
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
final long token = proto.start(fieldId);
if (effect instanceof VibrationEffect.OneShot) {
dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0fa4fe1..10393f6 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -29,8 +29,7 @@
import java.util.Objects;
/** Controls vibration scaling. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationScaler {
+final class VibrationScaler {
private static final String TAG = "VibrationScaler";
// Scale levels. Each level, except MUTE, is defined as the delta between the current setting
@@ -56,7 +55,7 @@
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
- public VibrationScaler(Context context, VibrationSettings settingsController) {
+ VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
mDefaultVibrationAmplitude = context.getResources().getInteger(
com.android.internal.R.integer.config_defaultVibrationAmplitude);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8910bdf..334129d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -39,20 +39,18 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.android.server.VibratorServiceDumpProto;
import java.util.ArrayList;
import java.util.List;
/** Controls all the system settings related to vibration. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationSettings {
+final class VibrationSettings {
private static final String TAG = "VibrationSettings";
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
/** Listener for changes on vibration settings. */
- public interface OnVibratorSettingsChanged {
+ interface OnVibratorSettingsChanged {
/** Callback triggered when any of the vibrator settings change. */
void onChange();
}
@@ -86,7 +84,7 @@
@GuardedBy("mLock")
private boolean mLowPowerMode;
- public VibrationSettings(Context context, Handler handler) {
+ VibrationSettings(Context context, Handler handler) {
mContext = context;
mVibrator = context.getSystemService(Vibrator.class);
mAudioManager = context.getSystemService(AudioManager.class);
@@ -345,17 +343,17 @@
/** Write current settings into given {@link ProtoOutputStream}. */
public void dumpProto(ProtoOutputStream proto) {
synchronized (mLock) {
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
mHapticFeedbackIntensity);
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
mVibrator.getDefaultHapticFeedbackIntensity());
- proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
mNotificationIntensity);
- proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
mVibrator.getDefaultNotificationVibrationIntensity());
- proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
mRingIntensity);
- proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
mVibrator.getDefaultRingVibrationIntensity());
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bee66637..3893267 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -42,8 +42,7 @@
import java.util.PriorityQueue;
/** Plays a {@link Vibration} in dedicated thread. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationThread extends Thread implements IBinder.DeathRecipient {
+final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private static final String TAG = "VibrationThread";
private static final boolean DEBUG = false;
@@ -54,7 +53,7 @@
private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
/** Callbacks for playing a {@link Vibration}. */
- public interface VibrationCallbacks {
+ interface VibrationCallbacks {
/**
* Callback triggered before starting a synchronized vibration step. This will be called
@@ -92,14 +91,7 @@
@GuardedBy("mLock")
private boolean mForceStop;
- // TODO(b/159207608): Remove this constructor once VibratorService is removed
- public VibrationThread(Vibration vib, VibratorController vibrator,
- PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
- VibrationCallbacks callbacks) {
- this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
- }
-
- public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
+ VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
mVibration = vib;
@@ -176,7 +168,7 @@
}
}
- public Vibration getVibration() {
+ Vibration getVibration() {
return mVibration;
}
@@ -286,12 +278,6 @@
return filteredEffects;
}
- private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
- SparseArray<VibratorController> array = new SparseArray<>(1);
- array.put(controller.getVibratorInfo().getId(), controller);
- return array;
- }
-
/**
* Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
* startIndex} until the next time it's vibrating amplitude is zero.
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 9dcf12c..95f6059 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -32,8 +32,7 @@
import libcore.util.NativeAllocationRegistry;
/** Controls a single vibrator. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibratorController {
+final class VibratorController {
private static final String TAG = "VibratorController";
private final Object mLock = new Object();
@@ -99,12 +98,12 @@
static native void vibratorAlwaysOnDisable(long nativePtr, long id);
- public VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
+ VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
this(vibratorId, listener, new NativeWrapper());
}
@VisibleForTesting
- public VibratorController(int vibratorId, OnVibrationCompleteListener listener,
+ VibratorController(int vibratorId, OnVibrationCompleteListener listener,
NativeWrapper nativeWrapper) {
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
@@ -142,11 +141,6 @@
}
}
- @VisibleForTesting
- public NativeWrapper getNativeWrapper() {
- return mNativeWrapper;
- }
-
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() {
return mVibratorInfo;
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
similarity index 82%
rename from services/core/java/com/android/server/VibratorManagerService.java
rename to services/core/java/com/android/server/vibrator/VibratorManagerService.java
index e7e5d67..1750854 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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.
@@ -14,13 +14,18 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
@@ -51,12 +56,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import libcore.util.NativeAllocationRegistry;
@@ -74,6 +75,7 @@
/** System implementation of {@link IVibratorManagerService}. */
public class VibratorManagerService extends IVibratorManagerService.Stub {
private static final String TAG = "VibratorManagerService";
+ private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final boolean DEBUG = false;
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
@@ -89,7 +91,7 @@
@Override
public void onStart() {
mService = new VibratorManagerService(getContext(), new Injector());
- publishBinderService("vibrator_manager", mService);
+ publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
}
@Override
@@ -105,6 +107,7 @@
private final Object mLock = new Object();
private final Context mContext;
+ private final String mSystemUiPackage;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final Handler mHandler;
@@ -128,6 +131,26 @@
private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate;
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ synchronized (mLock) {
+ // When the system is entering a non-interactive state, we want
+ // to cancel vibrations in case a misbehaving app has abandoned
+ // them. However it may happen that the system is currently playing
+ // haptic feedback as part of the transition. So we don't cancel
+ // system vibrations.
+ if (mCurrentVibration != null
+ && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
+ mNextVibration = null;
+ mCurrentVibration.cancel();
+ }
+ }
+ }
+ }
+ };
+
static native long nativeInit(OnSyncedVibrationCompleteListener listener);
static native long nativeGetFinalizer();
@@ -155,6 +178,9 @@
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+ mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+ .getSystemUiServiceComponent().getPackageName();
+
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
@@ -184,6 +210,12 @@
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).off();
}
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ context.registerReceiver(mIntentReceiver, filter);
+
+ injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
}
/** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -320,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;
@@ -334,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);
}
@@ -371,7 +400,7 @@
mVibratorManagerRecords.record(mCurrentExternalVibration);
mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null;
- // TODO(b/167946816): set external control to false
+ setExternalControl(false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -432,6 +461,12 @@
}
}
+ private void setExternalControl(boolean externalControl) {
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mVibrators.valueAt(i).setExternalControl(externalControl);
+ }
+ }
+
@GuardedBy("mLock")
private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
for (int i = 0; i < vib.effects.size(); i++) {
@@ -453,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);
}
@@ -507,6 +542,12 @@
}
@GuardedBy("mLock")
+ private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
+ vib.end(status);
+ mVibratorManagerRecords.record(vib);
+ }
+
+ @GuardedBy("mLock")
private void reportFinishedVibrationLocked(Vibration.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -551,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;
}
@@ -827,6 +868,13 @@
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean isSystemHapticFeedback(Vibration vib) {
+ if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
+ return false;
+ }
+ return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
+ }
+
@GuardedBy("mLock")
private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
for (int i = 0; i < mVibrators.size(); i++) {
@@ -859,6 +907,10 @@
VibratorController.OnVibrationCompleteListener listener) {
return new VibratorController(vibratorId, listener);
}
+
+ void addService(String name, IBinder service) {
+ ServiceManager.addService(name, service);
+ }
}
/**
@@ -908,7 +960,7 @@
/** Listener for synced vibration completion callbacks from native. */
@VisibleForTesting
- public interface OnSyncedVibrationCompleteListener {
+ interface OnSyncedVibrationCompleteListener {
/** Callback triggered when synced vibration is complete. */
void onComplete(long vibrationId);
@@ -1088,14 +1140,14 @@
}
pw.println();
pw.println(" mCurrentVibration:");
- pw.println(" " + mCurrentVibration == null
- ? null : mCurrentVibration.getVibration().getDebugInfo());
+ pw.println(" " + (mCurrentVibration == null
+ ? null : mCurrentVibration.getVibration().getDebugInfo()));
pw.println(" mNextVibration:");
- pw.println(" " + mNextVibration == null
- ? null : mNextVibration.getVibration().getDebugInfo());
+ pw.println(" " + (mNextVibration == null
+ ? null : mNextVibration.getVibration().getDebugInfo()));
pw.println(" mCurrentExternalVibration:");
- pw.println(" " + mCurrentExternalVibration == null
- ? null : mCurrentExternalVibration.getDebugInfo());
+ pw.println(" " + (mCurrentExternalVibration == null
+ ? null : mCurrentExternalVibration.getDebugInfo()));
pw.println();
pw.println(" mVibrationSettings=" + mVibrationSettings);
for (int i = 0; i < mPreviousVibrations.size(); i++) {
@@ -1108,6 +1160,7 @@
}
}
+ pw.println();
pw.println(" Previous external vibrations:");
for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
pw.println(" " + info);
@@ -1122,52 +1175,193 @@
mVibrationSettings.dumpProto(proto);
if (mCurrentVibration != null) {
mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_VIBRATION);
+ VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
if (mCurrentExternalVibration != null) {
mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+ VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
boolean isVibrating = false;
boolean isUnderExternalControl = false;
for (int i = 0; i < mVibrators.size(); i++) {
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
isVibrating |= mVibrators.valueAt(i).isVibrating();
isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
}
- proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
- proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+ proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
isUnderExternalControl);
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_RINGTONE)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_NOTIFICATION)) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_ALARM)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_UNKNOWN)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ long fieldId;
+ switch (mPreviousVibrations.keyAt(i)) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ fieldId = VibratorManagerServiceDumpProto
+ .PREVIOUS_NOTIFICATION_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_ALARM:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+ break;
+ default:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
+ }
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+ info.dumpProto(proto, fieldId);
+ }
}
for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ info.dumpProto(proto,
+ VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
}
}
proto.flush();
}
}
+ /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
+ private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+ ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
+
+ @Override
+ public int onExternalVibrationStart(ExternalVibration vib) {
+ if (!hasExternalControlCapability()) {
+ return IExternalVibratorService.SCALE_MUTE;
+ }
+ if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+ vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ + " tried to play externally controlled vibration"
+ + " without VIBRATE permission, ignoring.");
+ return IExternalVibratorService.SCALE_MUTE;
+ }
+
+ int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
+ vib.getVibrationAttributes());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+ endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
+ } else {
+ endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
+ }
+ return vibHolder.scale;
+ }
+
+ VibrationThread cancelingVibration = null;
+ int scale;
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ // We are already playing this external vibration, so we can return the same
+ // scale calculated in the previous call to this method.
+ return mCurrentExternalVibration.scale;
+ }
+ if (mCurrentExternalVibration == null) {
+ // If we're not under external control right now, then cancel any normal
+ // vibration that may be playing and ready the vibrator for external control.
+ if (mCurrentVibration != null) {
+ mNextVibration = null;
+ mCurrentVibration.cancel();
+ cancelingVibration = mCurrentVibration;
+ }
+ } else {
+ endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+ }
+ // At this point we either have an externally controlled vibration playing, or
+ // no vibration playing. Since the interface defines that only one externally
+ // controlled vibration can play at a time, by returning something other than
+ // SCALE_MUTE from this function we can be assured that if we are currently
+ // playing vibration, it will be muted in favor of the new vibration.
+ //
+ // Note that this doesn't support multiple concurrent external controls, as we
+ // would need to mute the old one still if it came from a different controller.
+ mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+ mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+ vib.linkToDeath(mCurrentExternalDeathRecipient);
+ mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
+ vib.getVibrationAttributes().getUsage());
+ scale = mCurrentExternalVibration.scale;
+ }
+
+ if (cancelingVibration != null) {
+ try {
+ cancelingVibration.join();
+ } catch (InterruptedException e) {
+ Slog.w("Interrupted while waiting for vibration to finish before starting "
+ + "external control", e);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator going under external control.");
+ }
+ setExternalControl(true);
+ if (DEBUG) {
+ Slog.e(TAG, "Playing external vibration: " + vib);
+ }
+ return scale;
+ }
+
+ @Override
+ public void onExternalVibrationStop(ExternalVibration vib) {
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ if (DEBUG) {
+ Slog.e(TAG, "Stopping external vibration" + vib);
+ }
+ stopExternalVibrateLocked(Vibration.Status.FINISHED);
+ }
+ }
+ }
+
+ private void stopExternalVibrateLocked(Vibration.Status status) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
+ try {
+ if (mCurrentExternalVibration == null) {
+ return;
+ }
+ endVibrationLocked(mCurrentExternalVibration, status);
+ mCurrentExternalVibration.externalVibration.unlinkToDeath(
+ mCurrentExternalDeathRecipient);
+ mCurrentExternalDeathRecipient = null;
+ mCurrentExternalVibration = null;
+ setExternalControl(false);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private boolean hasExternalControlCapability() {
+ for (int i = 0; i < mVibrators.size(); i++) {
+ if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "External vibration finished because binder died");
+ }
+ stopExternalVibrateLocked(Vibration.Status.CANCELLED);
+ }
+ }
+ }
+ }
+ }
+
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
private final class VibratorManagerShellCommand extends ShellCommand {
public static final String SHELL_PACKAGE_NAME = "com.android.shell";
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/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 771b712..c63a0f0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -134,10 +134,10 @@
}
@Override
- public void activityResumed(IBinder token) {
+ public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
- ActivityRecord.activityResumedLocked(token);
+ ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
}
Binder.restoreCallingIdentity(origId);
}
@@ -692,6 +692,18 @@
}
/**
+ * Splash screen view is attached to activity.
+ */
+ @Override
+ public void splashScreenAttached(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ ActivityRecord.splashScreenAttachedLocked(token);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ /**
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
*
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 68a2c5d..9bf6df4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -42,6 +42,8 @@
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -243,6 +245,7 @@
import android.app.servertransaction.StartActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.app.servertransaction.TopResumedActivityChangeItem;
+import android.app.servertransaction.TransferSplashScreenViewStateItem;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.Intent;
@@ -300,6 +303,7 @@
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
import android.window.IRemoteTransition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -669,6 +673,30 @@
boolean startingDisplayed;
boolean startingMoved;
+ boolean mHandleExitSplashScreen;
+ @TransferSplashScreenState int mTransferringSplashScreenState =
+ TRANSFER_SPLASH_SCREEN_IDLE;
+
+ // Idle, can be triggered to do transfer if needed.
+ static final int TRANSFER_SPLASH_SCREEN_IDLE = 0;
+ // requesting a copy from shell.
+ static final int TRANSFER_SPLASH_SCREEN_COPYING = 1;
+ // attach the splash screen view to activity.
+ static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2;
+ // client has taken over splash screen view.
+ static final int TRANSFER_SPLASH_SCREEN_FINISH = 3;
+
+ @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = {
+ TRANSFER_SPLASH_SCREEN_IDLE,
+ TRANSFER_SPLASH_SCREEN_COPYING,
+ TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT,
+ TRANSFER_SPLASH_SCREEN_FINISH,
+ })
+ @interface TransferSplashScreenState {}
+
+ // How long we wait until giving up transfer splash screen.
+ private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
+
// TODO: Have a WindowContainer state for tracking exiting/deferred removal.
boolean mIsExiting;
// Force an app transition to be ran in the case the visibility of the app did not change.
@@ -1727,6 +1755,7 @@
if (_createTime > 0) {
createTime = _createTime;
}
+ mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
}
/**
@@ -1807,7 +1836,85 @@
return hasProcess() && app.hasThread();
}
- boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
+ /**
+ * Evaluate the theme for a starting window.
+ * @param originalTheme The original theme which read from activity or application.
+ * @param replaceTheme The replace theme which requested from starter.
+ * @return Resolved theme.
+ */
+ private int evaluateStartingWindowTheme(String pkg, int originalTheme, int replaceTheme) {
+ // Skip if the package doesn't want a starting window.
+ if (!validateStartingWindowTheme(pkg, originalTheme)) {
+ return 0;
+ }
+ int selectedTheme = originalTheme;
+ if (replaceTheme != 0 && validateStartingWindowTheme(pkg, replaceTheme)) {
+ // allow to replace theme
+ selectedTheme = replaceTheme;
+ }
+ return selectedTheme;
+ }
+
+ private boolean validateStartingWindowTheme(String pkg, int theme) {
+ // If this is a translucent window, then don't show a starting window -- the current
+ // effect (a full-screen opaque starting window that fades away to the real contents
+ // when it is ready) does not work for this.
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
+ if (theme != 0) {
+ AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+ com.android.internal.R.styleable.Window,
+ mWmService.mCurrentUserId);
+ if (ent == null) {
+ // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
+ // see that.
+ return false;
+ }
+ final boolean windowIsTranslucent = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+ final boolean windowIsFloating = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsFloating, false);
+ final boolean windowShowWallpaper = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+ final boolean windowDisableStarting = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowDisablePreview, false);
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
+ "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+ windowIsTranslucent, windowIsFloating, windowShowWallpaper,
+ windowDisableStarting);
+ if (windowIsTranslucent || windowIsFloating || windowDisableStarting) {
+ return false;
+ }
+ if (windowShowWallpaper
+ && getDisplayContent().mWallpaperController.getWallpaperTarget() != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void applyStartingWindowTheme(String pkg, int theme) {
+ if (theme != 0) {
+ AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+ com.android.internal.R.styleable.Window,
+ mWmService.mCurrentUserId);
+ if (ent == null) {
+ return;
+ }
+ final boolean windowShowWallpaper = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+ if (windowShowWallpaper && getDisplayContent().mWallpaperController
+ .getWallpaperTarget() == null) {
+ // If this theme is requesting a wallpaper, and the wallpaper
+ // is not currently visible, then this effectively serves as
+ // an opaque window and our starting window transition animation
+ // can still work. We just need to make sure the starting window
+ // is also showing the wallpaper.
+ windowFlags |= FLAG_SHOW_WALLPAPER;
+ }
+ }
+ }
+
+ boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated) {
@@ -1850,49 +1957,12 @@
return createSnapshot(snapshot, typeParameter);
}
- // If this is a translucent window, then don't show a starting window -- the current
- // effect (a full-screen opaque starting window that fades away to the real contents
- // when it is ready) does not work for this.
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
- if (theme != 0) {
- AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
- com.android.internal.R.styleable.Window,
- mWmService.mCurrentUserId);
- if (ent == null) {
- // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
- // see that.
- return false;
- }
- final boolean windowIsTranslucent = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- final boolean windowIsFloating = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsFloating, false);
- final boolean windowShowWallpaper = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowShowWallpaper, false);
- final boolean windowDisableStarting = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowDisablePreview, false);
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
- windowIsTranslucent, windowIsFloating, windowShowWallpaper);
- if (windowIsTranslucent) {
- return false;
- }
- if (windowIsFloating || windowDisableStarting) {
- return false;
- }
- if (windowShowWallpaper) {
- if (getDisplayContent().mWallpaperController
- .getWallpaperTarget() == null) {
- // If this theme is requesting a wallpaper, and the wallpaper
- // is not currently visible, then this effectively serves as
- // an opaque window and our starting window transition animation
- // can still work. We just need to make sure the starting window
- // is also showing the wallpaper.
- windowFlags |= FLAG_SHOW_WALLPAPER;
- } else {
- return false;
- }
- }
+ // Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0
+ // but original theme is not 0, means this package doesn't want a starting window.
+ if (resolvedTheme == 0 && theme != 0) {
+ return false;
}
+ applyStartingWindowTheme(pkg, resolvedTheme);
if (transferStartingWindow(transferFrom)) {
return true;
@@ -1906,7 +1976,7 @@
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
mStartingData = new SplashScreenStartingData(mWmService, pkg,
- theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+ resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
getMergedOverrideConfiguration(), typeParameter);
scheduleAddStartingWindow();
return true;
@@ -2031,7 +2101,118 @@
return snapshot.getRotation() == targetRotation;
}
+ /**
+ * See {@link SplashScreen#setOnExitAnimationListener}.
+ */
+ void setCustomizeSplashScreenExitAnimation(boolean enable) {
+ if (mHandleExitSplashScreen == enable) {
+ return;
+ }
+ mHandleExitSplashScreen = enable;
+ }
+
+ private final Runnable mTransferSplashScreenTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mAtmService.mGlobalLock) {
+ Slog.w(TAG, "Activity transferring splash screen timeout for "
+ + ActivityRecord.this + " state " + mTransferringSplashScreenState);
+ if (isTransferringSplashScreen()) {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ // TODO show default exit splash screen animation
+ removeStartingWindow();
+ }
+ }
+ }
+ };
+
+ private void scheduleTransferSplashScreenTimeout() {
+ mAtmService.mH.postDelayed(mTransferSplashScreenTimeoutRunnable,
+ TRANSFER_SPLASH_SCREEN_TIMEOUT);
+ }
+
+ private void removeTransferSplashScreenTimeout() {
+ mAtmService.mH.removeCallbacks(mTransferSplashScreenTimeoutRunnable);
+ }
+
+ private boolean transferSplashScreenIfNeeded() {
+ if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null
+ || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+ return false;
+ }
+ if (isTransferringSplashScreen()) {
+ return true;
+ }
+ requestCopySplashScreen();
+ return isTransferringSplashScreen();
+ }
+
+ private boolean isTransferringSplashScreen() {
+ return mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT
+ || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+ }
+
+ private void requestCopySplashScreen() {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
+ if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ }
+ scheduleTransferSplashScreenTimeout();
+ }
+
+ /**
+ * Receive the splash screen data from shell, sending to client.
+ * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy.
+ */
+ void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
+ removeTransferSplashScreenTimeout();
+ // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
+ // either way, abort and reset the sequence.
+ if (parcelable == null
+ || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+ if (parcelable != null) {
+ parcelable.clearIfNeeded();
+ }
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ return;
+ }
+ // schedule attach splashScreen to client
+ try {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+ scheduleTransferSplashScreenTimeout();
+ } catch (Exception e) {
+ Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ parcelable.clearIfNeeded();
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ }
+ }
+
+ private void onSplashScreenAttachComplete() {
+ removeTransferSplashScreenTimeout();
+ // Client has draw the splash screen, so we can remove the starting window.
+ if (mStartingWindow != null) {
+ mStartingWindow.hide(false, false);
+ }
+ try {
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
+ } catch (Exception e) {
+ Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
+ }
+ // no matter what, remove the starting window.
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ }
+
void removeStartingWindow() {
+ if (transferSplashScreenIfNeeded()) {
+ return;
+ }
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
if (mStartingWindow == null) {
if (mStartingData != null) {
// Starting window has not been added yet, but it is scheduled to be added.
@@ -5092,7 +5273,7 @@
}
}
- static void activityResumedLocked(IBinder token) {
+ static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r);
if (r == null) {
@@ -5100,12 +5281,22 @@
// been removed (e.g. destroy timeout), so the token could be null.
return;
}
+ r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit);
r.setSavedState(null /* savedState */);
r.mDisplayContent.handleActivitySizeCompatModeIfNeeded(r);
r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
}
+ static void splashScreenAttachedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ Slog.w(TAG, "splashScreenTransferredLocked cannot find activity");
+ return;
+ }
+ r.onSplashScreenAttachComplete();
+ }
+
/**
* Once we know that we have asked an application to put an activity in the resumed state
* (either by launching it or explicitly telling it), this function updates the rest of our
@@ -5187,6 +5378,7 @@
}
}
+ mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
}
@@ -5931,7 +6123,13 @@
pendingVoiceInteractionStart = false;
}
- void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+ void showStartingWindow(boolean taskSwitch) {
+ showStartingWindow(null /* prev */, false /* newTask */, taskSwitch,
+ 0 /* splashScreenTheme */);
+ }
+
+ void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+ int splashScreenTheme) {
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
@@ -5944,7 +6142,10 @@
final CompatibilityInfo compatInfo =
mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
- final boolean shown = addStartingWindow(packageName, theme,
+
+ final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme,
+ splashScreenTheme);
+ final boolean shown = addStartingWindow(packageName, resolvedTheme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3456e51..37fda4c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -772,11 +772,6 @@
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
}
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target));
- ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent,
- mRequest.activityInfo,
- mService.getProcessController(mRequest.caller),
- mSupervisor);
- newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle());
heavy.updateIntentForHeavyWeightActivity(newIntent);
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
mRequest.activityInfo.packageName);
@@ -2002,8 +1997,7 @@
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
- targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ targetTaskTop.showStartingWindow(true /* taskSwitch */);
} else if (mDoResume) {
// Make sure the root task and its belonging display are moved to topmost.
mTargetRootTask.moveToFront("intentActivityFound");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7d2075c..94379b1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -574,4 +574,26 @@
* @return Whether the package is the base of any locked task
*/
public abstract boolean isBaseOfLockedTask(String packageName);
+
+ /**
+ * Create an interface to update configuration for an application.
+ */
+ public abstract PackageConfigurationUpdater createPackageConfigurationUpdater();
+
+ /**
+ * An interface to update configuration for an application, and will persist override
+ * configuration for this package.
+ */
+ public interface PackageConfigurationUpdater {
+ /**
+ * Sets the dark mode for the current application. This setting is persisted and will
+ * override the system configuration for this application.
+ */
+ PackageConfigurationUpdater setNightMode(int nightMode);
+
+ /**
+ * Commit changes.
+ */
+ void commit() throws RemoteException;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f16a646..2e98c2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -224,6 +224,7 @@
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerTransaction;
@@ -451,6 +452,7 @@
/** The controller for all operations related to locktask. */
private LockTaskController mLockTaskController;
private ActivityStartController mActivityStartController;
+ PackageConfigPersister mPackageConfigPersister;
boolean mSuppressResizeConfigChanges;
@@ -866,6 +868,7 @@
setRecentTasks(new RecentTasks(this, mTaskSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mTaskSupervisor.getKeyguardController();
+ mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
}
public void onActivityManagerInternalAdded() {
@@ -2027,8 +2030,7 @@
// We are reshowing a task, use a starting window to hide the initial draw delay
// so the transition can start earlier.
- topActivity.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ topActivity.showStartingWindow(true /* taskSwitch */);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3249,6 +3251,30 @@
}
/**
+ * A splash screen view has copied, pass it to an activity.
+ *
+ * @param taskId Id of task to handle the material to reconstruct the view.
+ * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+ * @hide
+ */
+ @Override
+ public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
+ throws RemoteException {
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
+ "copySplashScreenViewFinish()");
+ synchronized (mGlobalLock) {
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_ATTACHED_TASK_ONLY);
+ if (task != null) {
+ final ActivityRecord r = task.getTopWaitSplashScreenActivity();
+ if (r != null) {
+ r.onCopySplashScreenFinish(parcelable);
+ }
+ }
+ }
+ }
+
+ /**
* Puts the given activity in picture in picture mode if possible.
*
* @return true if the activity is now in picture-in-picture mode, or false if it could not
@@ -5433,6 +5459,7 @@
synchronized (mGlobalLock) {
mAppWarnings.onPackageUninstalled(name);
mCompatModePackages.handlePackageUninstalledLocked(name);
+ mPackageConfigPersister.onPackageUninstall(name);
}
}
@@ -6103,6 +6130,7 @@
public void removeUser(int userId) {
synchronized (mGlobalLock) {
mRootWindowContainer.removeUser(userId);
+ mPackageConfigPersister.removeUser(userId);
}
}
@@ -6200,6 +6228,8 @@
public void loadRecentTasksForUser(int userId) {
synchronized (mGlobalLock) {
mRecentTasks.loadUserRecentsLocked(userId);
+ // TODO renaming the methods(?)
+ mPackageConfigPersister.loadUserPackages(userId);
}
}
@@ -6308,5 +6338,54 @@
return getLockTaskController().isBaseOfLockedTask(packageName);
}
}
+
+ @Override
+ public PackageConfigurationUpdater createPackageConfigurationUpdater() {
+ synchronized (mGlobalLock) {
+ return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+ }
+ }
+ }
+
+ final class PackageConfigurationUpdaterImpl implements
+ ActivityTaskManagerInternal.PackageConfigurationUpdater {
+ private int mPid;
+ private int mNightMode;
+
+ PackageConfigurationUpdaterImpl(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+ mNightMode = nightMode;
+ return this;
+ }
+
+ @Override
+ public void commit() throws RemoteException {
+ if (mPid == 0) {
+ throw new RemoteException("Invalid process");
+ }
+ synchronized (mGlobalLock) {
+ final WindowProcessController wpc = mProcessMap.getProcess(mPid);
+ if (wpc == null) {
+ Slog.w(TAG, "Override application configuration: cannot find application");
+ return;
+ }
+ if (wpc.getNightMode() == mNightMode) {
+ return;
+ }
+ if (!wpc.setOverrideNightMode(mNightMode)) {
+ return;
+ }
+ wpc.updateNightModeForAllActivities(mNightMode);
+ mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
+ }
+ }
+
+ int getNightMode() {
+ return mNightMode;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d90e885..efcaaa4 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,6 +534,28 @@
return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
}
+ /**
+ * Overrides the night mode applied to this ConfigurationContainer.
+ * @return true if the nightMode has been changed.
+ */
+ public boolean setOverrideNightMode(int nightMode) {
+ final int currentUiMode = mFullConfiguration.uiMode;
+ final int currentNightMode = getNightMode();
+ final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
+ if (currentNightMode == validNightMode) {
+ return false;
+ }
+ mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+ mRequestsTmpConfig.uiMode = validNightMode
+ | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+ onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ return true;
+ }
+
+ int getNightMode() {
+ return mFullConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ }
+
public boolean isActivityTypeDream() {
return getActivityType() == ACTIVITY_TYPE_DREAM;
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f075d85..759b7fe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -429,6 +429,10 @@
void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) {
if (mOrganizer == organizer) return;
+ if (mDisplayContent == null || !mDisplayContent.isTrusted()) {
+ throw new IllegalStateException(
+ "Don't organize or trigger events for unavailable or untrusted display.");
+ }
IDisplayAreaOrganizer lastOrganizer = mOrganizer;
// Update the new display area organizer before calling sendDisplayAreaVanished since it
// could result in a new SurfaceControl getting created that would notify the old organizer
@@ -500,6 +504,17 @@
return false;
}
+ @Override
+ void removeImmediately() {
+ setOrganizer(null);
+ super.removeImmediately();
+ }
+
+ @Override
+ DisplayArea getDisplayArea() {
+ return this;
+ }
+
/**
* DisplayArea that contains WindowTokens, and orders them according to their type.
*/
@@ -580,11 +595,6 @@
}
}
- @Override
- DisplayArea getDisplayArea() {
- return this;
- }
-
/**
* DisplayArea that can be dimmed.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index ed44876..2beb378 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -21,6 +21,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.DisplayArea.Type.ANY;
+import android.annotation.Nullable;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
@@ -77,6 +78,11 @@
mService.enforceTaskPermission(func);
}
+ @Nullable
+ IDisplayAreaOrganizer getOrganizerByFeature(int featureId) {
+ return mOrganizersByFeatureIds.get(featureId);
+ }
+
@Override
public ParceledListSlice<DisplayAreaAppearedInfo> registerOrganizer(
IDisplayAreaOrganizer organizer, int feature) {
@@ -100,10 +106,18 @@
}
final List<DisplayAreaAppearedInfo> displayAreaInfos = new ArrayList<>();
- mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
- if (da.mFeatureId != feature) return;
- displayAreaInfos.add(organizeDisplayArea(organizer, da,
- "DisplayAreaOrganizerController.registerOrganizer"));
+ mService.mRootWindowContainer.forAllDisplays(dc -> {
+ if (!dc.isTrusted()) {
+ ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER,
+ "Don't organize or trigger events for untrusted displayId=%d",
+ dc.getDisplayId());
+ return;
+ }
+ dc.forAllDisplayAreas((da) -> {
+ if (da.mFeatureId != feature) return;
+ displayAreaInfos.add(organizeDisplayArea(organizer, da,
+ "DisplayAreaOrganizerController.registerOrganizer"));
+ });
});
mOrganizersByFeatureIds.put(feature, organizer);
@@ -148,6 +162,10 @@
throw new IllegalArgumentException("createTaskDisplayArea unknown displayId="
+ displayId);
}
+ if (!display.isTrusted()) {
+ throw new IllegalArgumentException("createTaskDisplayArea untrusted displayId="
+ + displayId);
+ }
// The parentFeatureId can be either a RootDisplayArea or a TaskDisplayArea.
// Check if there is a RootDisplayArea with the given parentFeatureId.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 23eab98..86968ed 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -213,6 +213,7 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.IDisplayAreaOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -239,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;
@@ -662,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();
@@ -1074,6 +1072,7 @@
// Sets the display content for the children.
onDisplayChanged(this);
+ updateDisplayAreaOrganizers();
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
@@ -2712,6 +2711,30 @@
}
/**
+ * Checks for all non-organized {@link DisplayArea}s for if there is any existing organizer for
+ * their features. If so, registers them with the matched organizer.
+ */
+ @VisibleForTesting
+ void updateDisplayAreaOrganizers() {
+ if (!isTrusted()) {
+ // No need to update for untrusted display.
+ return;
+ }
+ forAllDisplayAreas(displayArea -> {
+ if (displayArea.isOrganized()) {
+ return;
+ }
+ // Check if we have a registered organizer for the DA feature.
+ final IDisplayAreaOrganizer organizer =
+ mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+ .getOrganizerByFeature(displayArea.mFeatureId);
+ if (organizer != null) {
+ displayArea.setOrganizer(organizer);
+ }
+ });
+ }
+
+ /**
* Returns true if the input point is within an app window.
*/
boolean pointWithinAppWindow(int x, int y) {
@@ -5527,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/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
new file mode 100644
index 0000000..1552a96
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -0,0 +1,380 @@
+/*
+ * 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.wm;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * Persist configuration for each package, only persist the change if some on attributes are
+ * different from the global configuration. This class only applies to packages with Activities.
+ */
+public class PackageConfigPersister {
+ private static final String TAG = PackageConfigPersister.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String TAG_CONFIG = "config";
+ private static final String ATTR_PACKAGE_NAME = "package_name";
+ private static final String ATTR_NIGHT_MODE = "night_mode";
+
+ private static final String PACKAGE_DIRNAME = "package_configs";
+ private static final String SUFFIX_FILE_NAME = "_config.xml";
+
+ private final PersisterQueue mPersisterQueue;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
+ new SparseArray<>();
+ @GuardedBy("mLock")
+ private final SparseArray<HashMap<String, PackageConfigRecord>> mModified =
+ new SparseArray<>();
+
+ private static File getUserConfigsDir(int userId) {
+ return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
+ }
+
+ PackageConfigPersister(PersisterQueue queue) {
+ mPersisterQueue = queue;
+ }
+
+ @GuardedBy("mLock")
+ void loadUserPackages(int userId) {
+ synchronized (mLock) {
+ final File userConfigsDir = getUserConfigsDir(userId);
+ final File[] configFiles = userConfigsDir.listFiles();
+ if (configFiles == null) {
+ Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir);
+ return;
+ }
+
+ for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) {
+ final File configFile = configFiles[fileIndex];
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: userId=" + userId
+ + ", configFile=" + configFile.getName());
+ }
+ if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) {
+ continue;
+ }
+
+ try (InputStream is = new FileInputStream(configFile)) {
+ final TypedXmlPullParser in = Xml.resolvePullParser(is);
+ int event;
+ String packageName = null;
+ int nightMode = MODE_NIGHT_AUTO;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+ && event != XmlPullParser.END_TAG) {
+ final String name = in.getName();
+ if (event == XmlPullParser.START_TAG) {
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: START_TAG name=" + name);
+ }
+ if (TAG_CONFIG.equals(name)) {
+ for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0;
+ --attIdx) {
+ final String attrName = in.getAttributeName(attIdx);
+ final String attrValue = in.getAttributeValue(attIdx);
+ switch (attrName) {
+ case ATTR_PACKAGE_NAME:
+ packageName = attrValue;
+ break;
+ case ATTR_NIGHT_MODE:
+ nightMode = Integer.parseInt(attrValue);
+ break;
+ }
+ }
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ if (packageName != null) {
+ final PackageConfigRecord initRecord =
+ findRecordOrCreate(mModified, packageName, userId);
+ initRecord.mNightMode = nightMode;
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: load one package " + initRecord);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId,
+ String packageName) {
+ synchronized (mLock) {
+ final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
+ }
+ if (modifiedRecord != null) {
+ container.setOverrideNightMode(modifiedRecord.mNightMode);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void updateFromImpl(String packageName, int userId,
+ ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+ synchronized (mLock) {
+ PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
+ record.mNightMode = impl.getNightMode();
+
+ if (record.isResetNightMode()) {
+ removePackage(record.mName, record.mUserId);
+ } else {
+ final PackageConfigRecord pendingRecord =
+ findRecord(mPendingWrite, record.mName, record.mUserId);
+ final PackageConfigRecord writeRecord;
+ if (pendingRecord == null) {
+ writeRecord = findRecordOrCreate(mPendingWrite, record.mName,
+ record.mUserId);
+ } else {
+ writeRecord = pendingRecord;
+ }
+ if (writeRecord.mNightMode == record.mNightMode) {
+ return;
+ }
+ writeRecord.mNightMode = record.mNightMode;
+ if (DEBUG) {
+ Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
+ }
+ mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void removeUser(int userId) {
+ synchronized (mLock) {
+ final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId);
+ final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId);
+ if ((modifyRecords == null || modifyRecords.size() == 0)
+ && (writeRecords == null || writeRecords.size() == 0)) {
+ return;
+ }
+ final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords);
+ tempList.forEach((name, record) -> {
+ removePackage(record.mName, record.mUserId);
+ });
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onPackageUninstall(String packageName) {
+ synchronized (mLock) {
+ for (int i = mModified.size() - 1; i > 0; i--) {
+ final int userId = mModified.keyAt(i);
+ removePackage(packageName, userId);
+ }
+ }
+ }
+
+ private void removePackage(String packageName, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId);
+ }
+ final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId);
+ if (record != null) {
+ removeRecord(mPendingWrite, record);
+ mPersisterQueue.removeItems(item ->
+ item.mRecord.mName == record.mName
+ && item.mRecord.mUserId == record.mUserId,
+ WriteProcessItem.class);
+ }
+
+ final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId);
+ if (modifyRecord != null) {
+ removeRecord(mModified, modifyRecord);
+ mPersisterQueue.addItem(new DeletePackageItem(userId, packageName),
+ false /* flush */);
+ }
+ }
+
+ // store a changed data so we don't need to get the process
+ static class PackageConfigRecord {
+ final String mName;
+ final int mUserId;
+ int mNightMode;
+
+ PackageConfigRecord(String name, int userId) {
+ mName = name;
+ mUserId = userId;
+ }
+
+ boolean isResetNightMode() {
+ return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageConfigRecord package name: " + mName + " userId " + mUserId
+ + " nightMode " + mNightMode;
+ }
+ }
+
+ private PackageConfigRecord findRecordOrCreate(
+ SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) {
+ HashMap<String, PackageConfigRecord> records = list.get(userId);
+ if (records == null) {
+ records = new HashMap<>();
+ list.put(userId, records);
+ }
+ PackageConfigRecord record = records.get(name);
+ if (record != null) {
+ return record;
+ }
+ record = new PackageConfigRecord(name, userId);
+ records.put(name, record);
+ return record;
+ }
+
+ private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+ String name, int userId) {
+ HashMap<String, PackageConfigRecord> packages = list.get(userId);
+ if (packages == null) {
+ return null;
+ }
+ return packages.get(name);
+ }
+
+ private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+ PackageConfigRecord record) {
+ final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId);
+ if (processes != null) {
+ processes.remove(record.mName);
+ }
+ }
+
+ private static class DeletePackageItem implements PersisterQueue.WriteQueueItem {
+ final int mUserId;
+ final String mPackageName;
+
+ DeletePackageItem(int userId, String packageName) {
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public void process() {
+ File userConfigsDir = getUserConfigsDir(mUserId);
+ if (!userConfigsDir.isDirectory()) {
+ return;
+ }
+ final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir,
+ mPackageName + SUFFIX_FILE_NAME));
+ if (atomicFile.exists()) {
+ atomicFile.delete();
+ }
+ }
+ }
+
+ private class WriteProcessItem implements PersisterQueue.WriteQueueItem {
+ final PackageConfigRecord mRecord;
+
+ WriteProcessItem(PackageConfigRecord record) {
+ mRecord = record;
+ }
+
+ @Override
+ public void process() {
+ // Write out one user.
+ byte[] data = null;
+ synchronized (mLock) {
+ try {
+ data = saveToXml();
+ } catch (Exception e) {
+ }
+ removeRecord(mPendingWrite, mRecord);
+ }
+ if (data != null) {
+ // Write out xml file while not holding mService lock.
+ FileOutputStream file = null;
+ AtomicFile atomicFile = null;
+ try {
+ File userConfigsDir = getUserConfigsDir(mRecord.mUserId);
+ if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) {
+ Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId
+ + ": " + userConfigsDir);
+ return;
+ }
+ atomicFile = new AtomicFile(new File(userConfigsDir,
+ mRecord.mName + SUFFIX_FILE_NAME));
+ file = atomicFile.startWrite();
+ file.write(data);
+ atomicFile.finishWrite(file);
+ } catch (IOException e) {
+ if (file != null) {
+ atomicFile.failWrite(file);
+ }
+ Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
+ }
+ }
+ }
+
+ private byte[] saveToXml() throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os);
+
+ xmlSerializer.startDocument(null, true);
+ if (DEBUG) {
+ Slog.d(TAG, "Writing package configuration=" + mRecord);
+ }
+ xmlSerializer.startTag(null, TAG_CONFIG);
+ xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
+ xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ xmlSerializer.endTag(null, TAG_CONFIG);
+ xmlSerializer.endDocument();
+ xmlSerializer.flush();
+
+ return os.toByteArray();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bd93e04..6fbeaa4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,8 +847,6 @@
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
- // Send any pending task-info changes that were queued-up during a layout deferment
- mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
@@ -861,6 +859,8 @@
}
}
+ // Send any pending task-info changes that were queued-up during a layout deferment
+ mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
checkAppTransitionReady(surfacePlacer);
@@ -2656,7 +2656,7 @@
void addStartingWindowsForVisibleActivities() {
forAllActivities((r) -> {
if (r.mVisibleRequested) {
- r.showStartingWindow(null /* prev */, false /* newTask */, true /*taskSwitch*/);
+ r.showStartingWindow(true /*taskSwitch*/);
}
});
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 94e14dd..ef4a40f 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -60,7 +60,7 @@
final Task task = activity.getTask();
if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
- activity.token)) {
+ activity.token, theme)) {
return new ShellStartingSurface(task);
}
return null;
@@ -125,7 +125,8 @@
return mService.mTaskSnapshotController
.createStartingSurface(activity, taskSnapshot);
}
- mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token);
+ mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
+ 0 /* launchTheme */);
return new ShellStartingSurface(task);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c8a997..8bd4dfd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -82,6 +82,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -3881,6 +3882,13 @@
});
}
+ ActivityRecord getTopWaitSplashScreenActivity() {
+ return getActivity((r) -> {
+ return r.mHandleExitSplashScreen
+ && r.mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+ });
+ }
+
boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
return r != null ? r.isFocusable()
@@ -4267,6 +4275,9 @@
if (mainWindow != null) {
info.mainWindowLayoutParams = mainWindow.getAttrs();
}
+ // If the developer has persist a different configuration, we need to override it to the
+ // starting window because persisted configuration does not effect to Task.
+ info.taskInfo.configuration.setTo(topActivity.getConfiguration());
}
final ActivityRecord topFullscreenActivity = getTopFullscreenActivity();
if (topFullscreenActivity != null) {
@@ -6558,10 +6569,9 @@
Slog.i(TAG, "Restarting because process died: " + next);
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+ } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
&& lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
- next.showStartingWindow(null /* prev */, false /* newTask */,
- false /* taskSwitch */);
+ next.showStartingWindow(false /* taskSwitch */);
}
mTaskSupervisor.startSpecificActivity(next, true, false);
return true;
@@ -6584,8 +6594,7 @@
next.hasBeenLaunched = true;
} else {
if (SHOW_APP_STARTING_PREVIEW) {
- next.showStartingWindow(null /* prev */, false /* newTask */,
- false /* taskSwich */);
+ next.showStartingWindow(false /* taskSwich */);
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
@@ -6746,7 +6755,10 @@
prev = null;
}
}
- r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
+ final int splashScreenThemeResId = options != null
+ ? options.getSplashScreenThemeResId() : 0;
+ r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
+ splashScreenThemeResId);
}
} else {
// If this is the first activity, don't do any fancy animations,
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9fac3f0..c0bce6b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -117,8 +117,11 @@
return mTaskOrganizer.asBinder();
}
- void addStartingWindow(Task task, IBinder appToken) {
+ void addStartingWindow(Task task, IBinder appToken, int launchTheme) {
final StartingWindowInfo info = task.getStartingWindowInfo();
+ if (launchTheme != 0) {
+ info.splashScreenThemeResId = launchTheme;
+ }
mDeferTaskOrgCallbacksConsumer.accept(() -> {
try {
mTaskOrganizer.addStartingWindow(info, appToken);
@@ -138,6 +141,16 @@
});
}
+ void copySplashScreenView(Task task) {
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ try {
+ mTaskOrganizer.copySplashScreenView(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
+ }
+ });
+ }
+
SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
if (!task.mCreatedByOrganizer && !visible) {
@@ -232,14 +245,18 @@
mUid = uid;
}
- void addStartingWindow(Task t, IBinder appToken) {
- mOrganizer.addStartingWindow(t, appToken);
+ void addStartingWindow(Task t, IBinder appToken, int launchTheme) {
+ mOrganizer.addStartingWindow(t, appToken, launchTheme);
}
void removeStartingWindow(Task t) {
mOrganizer.removeStartingWindow(t);
}
+ void copySplashScreenView(Task t) {
+ mOrganizer.copySplashScreenView(t);
+ }
+
/**
* Register this task with this state, but doesn't trigger the task appeared callback to
* the organizer.
@@ -465,14 +482,14 @@
return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
}
- boolean addStartingWindow(Task task, IBinder appToken) {
+ boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) {
final Task rootTask = task.getRootTask();
if (rootTask == null || rootTask.mTaskOrganizer == null) {
return false;
}
final TaskOrganizerState state =
mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.addStartingWindow(task, appToken);
+ state.addStartingWindow(task, appToken, launchTheme);
return true;
}
@@ -486,6 +503,17 @@
state.removeStartingWindow(task);
}
+ boolean copySplashScreenView(Task task) {
+ final Task rootTask = task.getRootTask();
+ if (rootTask == null || rootTask.mTaskOrganizer == null) {
+ return false;
+ }
+ final TaskOrganizerState state =
+ mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
+ state.copySplashScreenView(task);
+ return true;
+ }
+
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.addTask(task)) {
@@ -646,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/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c54c978..5dc5ab7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7514,8 +7514,8 @@
if (spec != null) {
result.setTo(spec);
}
- spec.scale *= windowState.mGlobalScale;
- return spec;
+ result.scale *= windowState.mGlobalScale;
+ return result;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 264a3b4..c362023 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -256,6 +256,7 @@
}
onConfigurationChanged(atm.getGlobalConfiguration());
+ mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
}
public void setPid(int pid) {
@@ -802,6 +803,13 @@
return false;
}
+ void updateNightModeForAllActivities(int nightMode) {
+ for (int i = mActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mActivities.get(i);
+ r.setOverrideNightMode(nightMode);
+ }
+ }
+
public void clearPackagePreferredForHomeActivities() {
synchronized (mAtm.mGlobalLock) {
for (int i = mActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1c4b034..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",
@@ -54,7 +55,7 @@
"com_android_server_UsbMidiDevice.cpp",
"com_android_server_UsbHostManager.cpp",
"com_android_server_vibrator_VibratorController.cpp",
- "com_android_server_VibratorManagerService.cpp",
+ "com_android_server_vibrator_VibratorManagerService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
@@ -149,7 +150,7 @@
"android.hardware.power@1.1",
"android.hardware.power-V1-cpp",
"android.hardware.power.stats@1.0",
- "android.hardware.power.stats-ndk_platform",
+ "android.hardware.power.stats-V1-ndk_platform",
"android.hardware.thermal@1.0",
"android.hardware.tv.input@1.0",
"android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 9a8942b..d076434 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,10 +1,6 @@
# Display
per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
-# Haptics
-per-file com_android_server_vibrator_VibratorController.cpp = michaelwr@google.com
-per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
-
# Input
per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
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/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4e47984..89b931d 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -29,7 +29,7 @@
#include <vibratorservice/VibratorHalController.h>
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
namespace V1_0 = android::hardware::vibrator::V1_0;
namespace V1_1 = android::hardware::vibrator::V1_1;
@@ -73,11 +73,8 @@
static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
- // TODO(b/167946816): remove this once VibratorService is removed.
- if (vibratorId < 0) {
- return std::move(std::make_unique<vibrator::HalController>());
- }
- vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
+ vibrator::ManagerHalController* manager =
+ android_server_vibrator_VibratorManagerService_getManager();
if (manager == nullptr) {
return nullptr;
}
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
similarity index 89%
rename from services/core/jni/com_android_server_VibratorManagerService.cpp
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index 5dbb71a..a47ab9d 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -26,7 +26,7 @@
#include <vibratorservice/VibratorManagerHalController.h>
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
namespace android {
@@ -64,7 +64,7 @@
const jobject mCallbackListener;
};
-vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() {
+vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager() {
std::lock_guard<std::mutex> lock(gManagerMutex);
return gManager;
}
@@ -156,10 +156,11 @@
service->hal()->cancelSynced();
}
+inline static constexpr auto sNativeInitMethodSignature =
+ "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+
static const JNINativeMethod method_table[] = {
- {"nativeInit",
- "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J",
- (void*)nativeInit},
+ {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
{"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
{"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities},
{"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
@@ -168,15 +169,15 @@
{"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
};
-int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
+int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
sJvm = jvm;
auto listenerClassName =
- "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener";
+ "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
- return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
- NELEM(method_table));
+ return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
+ method_table, NELEM(method_table));
}
}; // namespace android
diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
similarity index 84%
rename from services/core/jni/com_android_server_VibratorManagerService.h
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.h
index 22950c5..9924e24 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.h
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 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.
@@ -21,7 +21,7 @@
namespace android {
-extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager();
+extern vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager();
} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 34f6048..1815f0c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,8 +39,9 @@
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
-int register_android_server_VibratorManagerService(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);
@@ -54,10 +55,8 @@
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
- JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
- JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
int register_android_server_FaceService(JNIEnv* env);
@@ -90,9 +89,10 @@
register_android_server_UsbHostManager(env);
register_android_server_vr_VrManagerService(env);
register_android_server_vibrator_VibratorController(vm, env);
- register_android_server_VibratorManagerService(vm, env);
+ 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);
@@ -109,10 +109,8 @@
register_android_server_security_VerityUtils(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
- register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
- env);
- register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
- env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
+ register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
register_android_server_AdbDebuggingManager(env);
register_android_server_FaceService(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 bd10992..9772c2e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -62,6 +62,7 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -92,7 +93,6 @@
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -157,9 +157,9 @@
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.DeviceStateCache;
@@ -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;
@@ -781,8 +782,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);
@@ -1101,7 +1101,7 @@
*/
private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
int reason = getUnsafeOperationReason(operation);
- if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+ if (reason == OPERATION_SAFETY_REASON_NONE) return;
if (mSafetyChecker == null) {
// Happens on CTS after it's set just once (by OneTimeSafetyChecker)
@@ -1114,23 +1114,28 @@
/**
* Returns whether it's safe to execute the given {@code operation}, and why.
*/
- @UnsafeOperationReason
+ @OperationSafetyReason
int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
- return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+ return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE
: mSafetyChecker.getUnsafeOperationReason(operation);
}
@Override
public void setNextOperationSafety(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
DevicePolicyManager.operationToString(operation),
- DevicePolicyManager.unsafeOperationReasonToString(reason)));
+ DevicePolicyManager.operationSafetyReasonToString(reason)));
mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
}
+ @Override
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason);
+ }
+
// Used by DevicePolicyManagerServiceShellCommand
List<OwnerDto> listAllOwners() {
Preconditions.checkCallAuthorization(
@@ -1352,6 +1357,10 @@
return mContext.getSystemService(WifiManager.class);
}
+ UsbManager getUsbManager() {
+ return mContext.getSystemService(UsbManager.class);
+ }
+
@SuppressWarnings("AndroidFrameworkBinderIdentity")
long binderClearCallingIdentity() {
return Binder.clearCallingIdentity();
@@ -2923,6 +2932,7 @@
revertTransferOwnershipIfNecessaryLocked();
}
+ updateUsbDataSignal();
}
private void revertTransferOwnershipIfNecessaryLocked() {
@@ -6367,7 +6377,7 @@
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
+ () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(
caller.getUserId()));
}
@@ -7522,19 +7532,37 @@
sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
}
- private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
- sendActiveAdminCommand(action, extras, userHandle,
- mOwners.getProfileOwnerComponent(userHandle));
+ 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));
}
private void sendActiveAdminCommand(String action, Bundle extras,
- int userHandle, ComponentName receiverComponent) {
+ @UserIdInt int userId, ComponentName receiverComponent) {
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, "sending intent " + action + " to "
+ + receiverComponent.flattenToShortString() + " on user " + userId);
+ }
final Intent intent = new Intent(action);
intent.setComponent(receiverComponent);
if (extras != null) {
intent.putExtras(extras);
}
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle));
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
private void sendOwnerChangedBroadcast(String broadcast, int userId) {
@@ -8342,6 +8370,7 @@
deleteTransferOwnershipBundleLocked(userId);
toggleBackupServiceActive(userId, true);
applyManagedProfileRestrictionIfDeviceOwnerLocked();
+ setNetworkLoggingActiveInternal(false);
}
@Override
@@ -12224,6 +12253,32 @@
packageName, findInteractAcrossProfilesResetMode(packageName), userId);
}
+ @Override
+ public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
+ boolean isSafe) {
+ // TODO(b/178494483): use EventLog instead
+ // TODO(b/178494483): log metrics?
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
+ DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+ }
+
+ Preconditions.checkArgument(mSafetyChecker == checker,
+ "invalid checker: should be %s, was %s", mSafetyChecker, checker);
+
+ Bundle extras = new Bundle();
+ extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
+ extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
+
+ // TODO(b/178494483): add CTS test
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras);
+ for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+ sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras, profileOwnerId);
+ }
+ }
+
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
? AppOpsManager.MODE_ALLOWED
@@ -14096,7 +14151,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()) {
@@ -14104,11 +14161,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);
@@ -14126,7 +14183,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"
@@ -14146,6 +14209,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()),
@@ -14166,10 +14248,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 "
@@ -14188,7 +14272,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();
}
@@ -14215,7 +14301,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));
@@ -14225,8 +14313,8 @@
}
private boolean isNetworkLoggingEnabledInternalLocked() {
- ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+ ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+ return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
}
/*
@@ -14243,9 +14331,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()) {
@@ -14258,34 +14350,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);
@@ -14305,7 +14398,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());
}
/**
@@ -14369,8 +14462,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
@@ -16328,8 +16425,10 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+ final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ ? UserHandle.USER_SYSTEM : caller.getUserId();
if (!removeNonRequiredAppsForManagedDevice(
- caller.getUserId(),
+ deviceOwnerUserId,
provisioningParams.isLeaveAllSystemAppsEnabled(),
deviceAdmin)) {
throw new ServiceSpecificException(
@@ -16337,15 +16436,16 @@
"PackageManager failed to remove non required apps.");
}
+
if (!setActiveAdminAndDeviceOwner(
- caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) {
+ deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
throw new ServiceSpecificException(
PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
"Failed to set device owner.");
}
disallowAddUser();
- setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+ setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
provisioningParams.canDeviceOwnerGrantSensorsPermissions());
} catch (Exception e) {
DevicePolicyEventLogger
@@ -16388,30 +16488,42 @@
}
private boolean removeNonRequiredAppsForManagedDevice(
- int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
+ @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
Set<String> packagesToDelete = leaveAllSystemAppsEnabled
? Collections.emptySet()
: mOverlayPackagesProvider.getNonRequiredApps(
admin, userId, ACTION_PROVISION_MANAGED_DEVICE);
+
+ removeNonInstalledPackages(packagesToDelete, userId);
if (packagesToDelete.isEmpty()) {
+ Slog.i(LOG_TAG, "No packages to delete on user " + userId);
return true;
}
+
NonRequiredPackageDeleteObserver packageDeleteObserver =
new NonRequiredPackageDeleteObserver(packagesToDelete.size());
for (String packageName : packagesToDelete) {
- if (isPackageInstalledForUser(packageName, userId)) {
- Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
- mContext.getPackageManager().deletePackageAsUser(
- packageName,
- packageDeleteObserver,
- PackageManager.DELETE_SYSTEM_APP,
- userId);
- }
+ Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
+ mContext.getPackageManager().deletePackageAsUser(
+ packageName,
+ packageDeleteObserver,
+ PackageManager.DELETE_SYSTEM_APP,
+ userId);
}
Slog.i(LOG_TAG, "Waiting for non required apps to be deleted");
return packageDeleteObserver.awaitPackagesDeletion();
}
+ private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) {
+ final Set<String> toBeRemoved = new HashSet<>();
+ for (String packageName : packages) {
+ if (!isPackageInstalledForUser(packageName, userId)) {
+ toBeRemoved.add(packageName);
+ }
+ }
+ packages.removeAll(toBeRemoved);
+ }
+
private void disallowAddUser() {
if (mInjector.userManagerIsHeadlessSystemUserMode()) {
Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
@@ -16517,4 +16629,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/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 222c987..5484a14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -27,6 +27,7 @@
final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe";
+ private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason";
private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe";
private static final String CMD_LIST_OWNERS = "list-owners";
@@ -53,6 +54,8 @@
switch (cmd) {
case CMD_IS_SAFE_OPERATION:
return runIsSafeOperation(pw);
+ case CMD_IS_SAFE_OPERATION_BY_REASON:
+ return runIsSafeOperationByReason(pw);
case CMD_SET_SAFE_OPERATION:
return runSetSafeOperation(pw);
case CMD_LIST_OWNERS:
@@ -73,12 +76,13 @@
return -1;
}
-
private void showHelp(PrintWriter pw) {
pw.printf(" help\n");
pw.printf(" Prints this help text.\n\n");
pw.printf(" %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
pw.printf(" Checks if the give operation is safe \n\n");
+ pw.printf(" %s <REASON_ID>\n", CMD_IS_SAFE_OPERATION_BY_REASON);
+ pw.printf(" Checks if the operations are safe for the given reason\n\n");
pw.printf(" %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
pw.printf(" Emulates the result of the next call to check if the given operation is safe"
+ " \n\n");
@@ -89,10 +93,19 @@
private int runIsSafeOperation(PrintWriter pw) {
int operation = Integer.parseInt(getNextArgRequired());
int reason = mService.getUnsafeOperationReason(operation);
- boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+ boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
pw.printf("Operation %s is %b. Reason: %s\n",
DevicePolicyManager.operationToString(operation), safe,
- DevicePolicyManager.unsafeOperationReasonToString(reason));
+ DevicePolicyManager.operationSafetyReasonToString(reason));
+ return 0;
+ }
+
+ private int runIsSafeOperationByReason(PrintWriter pw) {
+ int reason = Integer.parseInt(getNextArgRequired());
+ boolean safe = mService.isSafeOperation(reason);
+ pw.printf("Operations affected by %s are %s\n",
+ DevicePolicyManager.operationSafetyReasonToString(reason),
+ (safe ? "SAFE" : "UNSAFE"));
return 0;
}
@@ -102,7 +115,7 @@
mService.setNextOperationSafety(operation, reason);
pw.printf("Next call to check operation %s will return %s\n",
DevicePolicyManager.operationToString(operation),
- DevicePolicyManager.unsafeOperationReasonToString(reason));
+ DevicePolicyManager.operationSafetyReasonToString(reason));
return 0;
}
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/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index 883f95d..7de1bd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,16 +15,18 @@
*/
package com.android.server.devicepolicy;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
import static android.app.admin.DevicePolicyManager.operationToString;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicySafetyChecker;
import android.util.Slog;
import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
import java.util.Objects;
@@ -43,10 +45,10 @@
private final DevicePolicyManagerService mService;
private final DevicePolicySafetyChecker mRealSafetyChecker;
private final @DevicePolicyOperation int mOperation;
- private final @UnsafeOperationReason int mReason;
+ private final @OperationSafetyReason int mReason;
OneTimeSafetyChecker(DevicePolicyManagerService service,
- @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
+ @DevicePolicyOperation int operation, @OperationSafetyReason int reason) {
mService = Objects.requireNonNull(service);
mOperation = operation;
mReason = reason;
@@ -55,24 +57,42 @@
}
@Override
- @UnsafeOperationReason
+ @OperationSafetyReason
public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
String name = operationToString(operation);
- int reason = UNSAFE_OPERATION_REASON_NONE;
+ Slog.i(TAG, "getUnsafeOperationReason(" + name + ")");
+ int reason = OPERATION_SAFETY_REASON_NONE;
if (operation == mOperation) {
reason = mReason;
} else {
Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
+ ", should be " + operationToString(mOperation));
}
- Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
- + unsafeOperationReasonToString(reason)
+ String reasonName = operationSafetyReasonToString(reason);
+ DevicePolicyManagerInternal dpmi = LocalServices
+ .getService(DevicePolicyManagerInternal.class);
+
+ Slog.i(TAG, "notifying " + reasonName + " is active");
+ dpmi.notifyUnsafeOperationStateChanged(this, reason, true);
+
+ Slog.i(TAG, "notifying " + reasonName + " is inactive");
+ dpmi.notifyUnsafeOperationStateChanged(this, reason, false);
+
+ Slog.i(TAG, "returning " + reasonName
+ " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
return reason;
}
@Override
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ boolean safe = mReason != reason;
+ Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe);
+
+ return safe;
+ }
+
+ @Override
public void onFactoryReset(IResultReceiver callback) {
throw new UnsupportedOperationException();
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 56cb3d1..886c1e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -69,6 +69,14 @@
static constexpr auto progressUpdateInterval = 1000ms;
static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
+
+ // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
+ static constexpr auto healthyDataLoaderUptime = 10min;
+ // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+ static constexpr auto minBindDelay = 10s;
+ static constexpr auto maxBindDelay = 10000s;
+ static constexpr auto bindDelayMultiplier = 10;
+ static constexpr auto bindDelayJitterDivider = 10;
};
static const Constants& constants() {
@@ -386,6 +394,28 @@
dprintf(fd, "}\n");
}
+bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+ if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+ return true;
+ }
+
+ // Check all permanent binds.
+ for (auto&& [_, bindPoint] : ifs.bindPoints) {
+ if (bindPoint.kind != BindKind::Permanent) {
+ continue;
+ }
+ const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir,
+ /*stopOnFirstIncomplete=*/true);
+ if (!progress.isError() && !progress.fullyLoaded()) {
+ LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir
+ << "], partial progress: " << progress.getProgress() * 100 << "%";
+ return true;
+ }
+ }
+
+ return false;
+}
+
void IncrementalService::onSystemReady() {
if (mSystemReady.exchange(true)) {
return;
@@ -396,8 +426,11 @@
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id &&
- ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
+ if (ifs->mountId != id) {
+ continue;
+ }
+
+ if (needStartDataLoaderLocked(*ifs)) {
mounts.push_back(ifs);
}
}
@@ -1539,6 +1572,11 @@
return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}
+template <class Duration>
+static constexpr auto castToMs(Duration d) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(d);
+}
+
// Extract lib files from zip, create new files in incfs and write data to them
// Lib files should be placed next to the APK file in the following matter:
// Example:
@@ -2134,9 +2172,43 @@
<< status << " (current " << mCurrentStatus << ")";
}
+Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+ std::unique_lock lock(mMutex);
+ const auto previousBindTs = mPreviousBindTs;
+ const auto now = Clock::now();
+ mPreviousBindTs = now;
+
+ const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
+ if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
+ nonCrashingInterval > Constants::healthyDataLoaderUptime) {
+ mPreviousBindDelay = 0ms;
+ return mPreviousBindDelay;
+ }
+
+ constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
+ constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay);
+
+ const auto bindDelayMs =
+ std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs),
+ maxBindDelayMs)
+ .count();
+ const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
+ const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+ mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
+
+ return mPreviousBindDelay;
+}
+
bool IncrementalService::DataLoaderStub::bind() {
+ const auto bindDelay = updateBindDelay();
+ if (bindDelay > 1s) {
+ LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
+ << bindDelay.count() / 1000 << "s";
+ }
+
bool result = false;
- auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
+ auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
+ this, &result);
if (!status.isOk() || !result) {
LOG(ERROR) << "Failed to bind a data loader for mount " << id();
return false;
@@ -2249,7 +2321,8 @@
listener = mStatusListener;
- if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
+ if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+ mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
// For unavailable, unbind from DataLoader to ensure proper re-commit.
setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
}
@@ -2544,6 +2617,9 @@
dprintf(fd, " blockIndex: %d\n", pendingRead.block);
dprintf(fd, " bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs);
}
+ dprintf(fd, " bind: %llds ago (delay: %llds)\n",
+ (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000),
+ (long long)(mPreviousBindDelay.count() / 1000));
dprintf(fd, " }\n");
const auto& params = mParams;
dprintf(fd, " dataLoaderParams: {\n");
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5d53bac..459ed32 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -245,7 +245,6 @@
void setTargetStatusLocked(int status);
bool fsmStep();
- bool fsmStep(int currentStatus, int targetStatus);
void onHealthStatus(StorageHealthListener healthListener, int healthStatus);
void updateHealthStatus(bool baseline = false);
@@ -259,6 +258,8 @@
BootClockTsUs getOldestPendingReadTs();
+ Milliseconds updateBindDelay();
+
void registerForPendingReads();
void unregisterFromPendingReads();
@@ -276,6 +277,9 @@
int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
TimePoint mTargetStatusTs = {};
+ TimePoint mPreviousBindTs = {};
+ Milliseconds mPreviousBindDelay = {};
+
std::string mHealthPath;
incfs::UniqueControl mHealthControl;
struct {
@@ -370,6 +374,8 @@
void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
std::string&& source, std::string&& target, BindKind kind);
+ bool needStartDataLoaderLocked(IncFsMount& ifs);
+
DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
content::pm::DataLoaderParamsParcel&& params,
const DataLoaderStatusListener* statusListener = nullptr,
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 25d3f77..659d650 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -70,9 +70,10 @@
~RealDataLoaderManager() = default;
binder::Status bindToDataLoader(MountId mountId,
const content::pm::DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<content::pm::IDataLoaderStatusListener>& listener,
bool* _aidl_return) const final {
- return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return);
+ return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
}
binder::Status getDataLoader(MountId mountId,
sp<content::pm::IDataLoader>* _aidl_return) const final {
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 71fd3ac..d60035a 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -63,7 +63,7 @@
public:
virtual ~DataLoaderManagerWrapper() = default;
virtual binder::Status bindToDataLoader(
- MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+ MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs,
const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
virtual binder::Status getDataLoader(MountId mountId,
sp<content::pm::IDataLoader>* result) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8713f9d..ab491ef 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -208,8 +208,9 @@
EXPECT_TRUE(mDataLoaderHolder != nullptr);
}
- MOCK_CONST_METHOD4(bindToDataLoader,
+ MOCK_CONST_METHOD5(bindToDataLoader,
binder::Status(int32_t mountId, const DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return));
MOCK_CONST_METHOD2(getDataLoader,
@@ -217,11 +218,11 @@
MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId));
void bindToDataLoaderSuccess() {
- ON_CALL(*this, bindToDataLoader(_, _, _, _))
+ ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk));
}
void bindToDataLoaderFails() {
- ON_CALL(*this, bindToDataLoader(_, _, _, _))
+ ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Return(
(binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
}
@@ -234,6 +235,7 @@
.WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk));
}
binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
mId = mountId;
@@ -245,6 +247,40 @@
}
return binder::Status::ok();
}
+ binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+ << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+
binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
*_aidl_return = mDataLoader;
return binder::Status::ok();
@@ -261,6 +297,9 @@
void setDataLoaderStatusUnavailable() {
mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
}
+ void setDataLoaderStatusUnrecoverable() {
+ mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
+ }
binder::Status unbindFromDataLoaderOk(int32_t id) {
if (mDataLoader) {
if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -676,7 +715,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
mVold->mountIncFsFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -686,7 +725,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
mVold->mountIncFsInvalidControlParcel();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
TemporaryDir tempDir;
int storageId =
@@ -698,7 +737,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
mVold->mountIncFsSuccess();
mIncFs->makeFileFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
@@ -712,7 +751,7 @@
mVold->mountIncFsSuccess();
mIncFs->makeFileSuccess();
mVold->bindMountFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
@@ -727,7 +766,7 @@
mIncFs->makeFileSuccess();
mVold->bindMountSuccess();
mDataLoaderManager->bindToDataLoaderFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -755,11 +794,11 @@
mIncrementalService->deleteStorage(storageId);
}
-TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
- EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
- EXPECT_CALL(*mDataLoader, start(_)).Times(2);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(6);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
@@ -768,13 +807,38 @@
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
// Simulated crash/other connection breakage.
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
mDataLoaderManager->setDataLoaderStatusDestroyed();
}
TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -793,7 +857,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -811,7 +875,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -827,12 +891,30 @@
mDataLoaderManager->setDataLoaderStatusUnavailable();
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) {
+ mDataLoader->initializeCreateOkNoStatus();
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+ mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+}
+
TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
mIncFs->waitForPendingReadsSuccess();
mIncFs->openMountSuccess();
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -856,7 +938,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
mIncFs->openMountSuccess();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -1406,7 +1488,7 @@
}
TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
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 97e7582..bd20464 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -195,6 +195,7 @@
import com.android.server.uri.UriGrantsManagerService;
import com.android.server.usage.UsageStatsService;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.vibrator.VibratorManagerService;
import com.android.server.vr.VrManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -1291,11 +1292,11 @@
t.traceBegin("startOtherServices");
final Context context = mSystemContext;
- VibratorService vibrator = null;
DynamicSystemService dynamicSystem = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
IpSecService ipSecService = null;
+ VpnManagerService vpnManager = null;
VcnManagementService vcnManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
@@ -1417,11 +1418,6 @@
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
t.traceEnd();
- t.traceBegin("StartVibratorService");
- vibrator = new VibratorService(context);
- ServiceManager.addService("vibrator", vibrator);
- t.traceEnd();
-
t.traceBegin("StartDynamicSystemService");
dynamicSystem = new DynamicSystemService(context);
ServiceManager.addService("dynamic_system", dynamicSystem);
@@ -1889,6 +1885,15 @@
networkPolicy.bindConnectivityManager(connectivity);
t.traceEnd();
+ t.traceBegin("StartVpnManagerService");
+ try {
+ vpnManager = VpnManagerService.create(context);
+ ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
+ } catch (Throwable e) {
+ reportWtf("starting VPN Manager Service", e);
+ }
+ t.traceEnd();
+
t.traceBegin("StartVcnManagementService");
try {
vcnManagement = VcnManagementService.create(context);
@@ -2490,14 +2495,6 @@
// It is now time to start up the app processes...
- t.traceBegin("MakeVibratorServiceReady");
- try {
- vibrator.systemReady();
- } catch (Throwable e) {
- reportWtf("making Vibrator Service ready", e);
- }
- t.traceEnd();
-
t.traceBegin("MakeLockSettingsServiceReady");
if (lockSettings != null) {
try {
@@ -2625,6 +2622,7 @@
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
final IpSecService ipSecServiceF = ipSecService;
+ final VpnManagerService vpnManagerF = vpnManager;
final VcnManagementService vcnManagementF = vcnManagement;
final WindowManagerService windowManagerF = wm;
final ConnectivityManager connectivityF = (ConnectivityManager)
@@ -2739,6 +2737,15 @@
reportWtf("making Connectivity Service ready", e);
}
t.traceEnd();
+ t.traceBegin("MakeVpnManagerServiceReady");
+ try {
+ if (vpnManagerF != null) {
+ vpnManagerF.systemReady();
+ }
+ } catch (Throwable e) {
+ reportWtf("making VpnManagerService ready", e);
+ }
+ t.traceEnd();
t.traceBegin("MakeVcnManagementServiceReady");
try {
if (vcnManagementF != null) {
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/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
new file mode 100644
index 0000000..195cc01
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -0,0 +1,725 @@
+/*
+ * 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.pm;
+
+import android.apex.ApexSessionInfo;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
+import android.os.SystemProperties;
+import android.os.storage.IStorageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class StagingManagerTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Mock private Context mContext;
+ @Mock private IStorageManager mStorageManager;
+ @Mock private ApexManager mApexManager;
+
+ private File mTmpDir;
+ private StagingManager mStagingManager;
+
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
+
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(PackageHelper.class)
+ .startMocking();
+
+ when(mStorageManager.supportsCheckpoint()).thenReturn(true);
+ when(mStorageManager.needsCheckpoint()).thenReturn(true);
+ when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+
+ when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
+ when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
+
+ mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
+ mStagingManager = new StagingManager(mContext, null, mApexManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
+ }
+
+ /**
+ * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
+ * check.
+ */
+ @Test
+ public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
+ throws Exception {
+ // Create 2 sessions with overlapping packages
+ StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
+ StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
+
+ mStagingManager.createSession(session1);
+ mStagingManager.createSession(session2);
+ // Session1 should not fail in spite of the overlapping packages
+ mStagingManager.checkNonOverlappingWithStagedSessions(session1);
+ // Session2 should fail due to overlapping packages
+ assertThrows(PackageManagerException.class,
+ () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
+ }
+
+ @Test
+ public void restoreSessions_nonParentSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+ session.setParentSessionId(1543);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_terminalSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+ session.setCommitted(true);
+ session.setSessionApplied();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception {
+ FakeStagedSession session1 = new FakeStagedSession(37);
+ session1.setCommitted(true);
+ FakeStagedSession session2 = new FakeStagedSession(57);
+ session2.setCommitted(true);
+
+ mStagingManager.restoreSessions(Arrays.asList(session1, session2), true);
+
+ assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+
+ assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+ }
+
+ @Test
+ public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE()
+ throws Exception {
+ FakeStagedSession session1 = new FakeStagedSession(37);
+ session1.setCommitted(true);
+ FakeStagedSession session2 = new FakeStagedSession(57);
+ session2.setCommitted(true);
+
+ when(mStorageManager.supportsCheckpoint()).thenReturn(false);
+
+ assertThrows(IllegalStateException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false));
+ }
+
+ @Test
+ public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception {
+ FakeStagedSession destroyedApkSession = new FakeStagedSession(23);
+ destroyedApkSession.setCommitted(true);
+ destroyedApkSession.setDestroyed(true);
+
+ FakeStagedSession destroyedApexSession = new FakeStagedSession(37);
+ destroyedApexSession.setCommitted(true);
+ destroyedApexSession.setDestroyed(true);
+ destroyedApexSession.setIsApex(true);
+
+ FakeStagedSession nonReadyApkSession = new FakeStagedSession(57);
+ nonReadyApkSession.setCommitted(true);
+
+ FakeStagedSession nonReadyApexSession = new FakeStagedSession(73);
+ nonReadyApexSession.setCommitted(true);
+ nonReadyApexSession.setIsApex(true);
+
+ FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101);
+ destroyedNonReadySession.setCommitted(true);
+ destroyedNonReadySession.setDestroyed(true);
+
+ FakeStagedSession regularApkSession = new FakeStagedSession(239);
+ regularApkSession.setCommitted(true);
+ regularApkSession.setSessionReady();
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(destroyedApkSession);
+ sessions.add(destroyedApexSession);
+ sessions.add(nonReadyApkSession);
+ sessions.add(nonReadyApexSession);
+ sessions.add(destroyedNonReadySession);
+ sessions.add(regularApkSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ assertThat(sessions).containsExactly(regularApkSession);
+ assertThat(destroyedApkSession.isDestroyed()).isTrue();
+ assertThat(destroyedApexSession.isDestroyed()).isTrue();
+ assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
+
+ mStagingManager.onBootCompletedBroadcastReceived();
+ assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
+ assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
+ }
+
+ @Test
+ public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ when(mApexManager.getSessions()).thenReturn(new SparseArray<>());
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+ + "staged session supposed to be activated");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+ apexSession1.setCommitted(true);
+ apexSession1.setIsApex(true);
+ apexSession1.setSessionReady();
+
+ FakeStagedSession apexSession2 = new FakeStagedSession(101);
+ apexSession2.setCommitted(true);
+ apexSession2.setIsApex(true);
+ apexSession2.setSessionReady();
+
+ FakeStagedSession apexSession3 = new FakeStagedSession(57);
+ apexSession3.setCommitted(true);
+ apexSession3.setIsApex(true);
+ apexSession3.setSessionReady();
+
+ ApexSessionInfo activationFailed = new ApexSessionInfo();
+ activationFailed.sessionId = 1543;
+ activationFailed.isActivationFailed = true;
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 101;
+ staged.isStaged = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, activationFailed);
+ apexdSessions.put(101, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession1);
+ sessions.add(apexSession2);
+ sessions.add(apexSession3);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession1.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat "
+ + "messages from apexd for more information.");
+
+ assertThat(apexSession2.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't "
+ + "activate nor fail. Marking it as failed anyway.");
+
+ assertThat(apexSession3.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+ + "staged session supposed to be activated");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 1543;
+ staged.isStaged = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't "
+ + "activate nor fail. Marking it as failed anyway.");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+ apexSession1.setCommitted(true);
+ apexSession1.setIsApex(true);
+ apexSession1.setSessionReady();
+
+ FakeStagedSession apexSession2 = new FakeStagedSession(101);
+ apexSession2.setCommitted(true);
+ apexSession2.setIsApex(true);
+ apexSession2.setSessionReady();
+
+ FakeStagedSession apexSession3 = new FakeStagedSession(57);
+ apexSession3.setCommitted(true);
+ apexSession3.setIsApex(true);
+ apexSession3.setSessionReady();
+
+ FakeStagedSession apexSession4 = new FakeStagedSession(37);
+ apexSession4.setCommitted(true);
+ apexSession4.setIsApex(true);
+ apexSession4.setSessionReady();
+
+ ApexSessionInfo activationFailed = new ApexSessionInfo();
+ activationFailed.sessionId = 1543;
+ activationFailed.isActivationFailed = true;
+
+ ApexSessionInfo activated = new ApexSessionInfo();
+ activated.sessionId = 101;
+ activated.isActivated = true;
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 57;
+ staged.isActivationFailed = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, activationFailed);
+ apexdSessions.put(101, activated);
+ apexdSessions.put(57, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession1);
+ sessions.add(apexSession2);
+ sessions.add(apexSession3);
+ sessions.add(apexSession4);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint was aborted.
+ verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false));
+ }
+
+ @Test
+ public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ ApexSessionInfo impossible = new ApexSessionInfo();
+ impossible.sessionId = 1543;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, impossible);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ private StagingManager.StagedSession createSession(int sessionId, String packageName,
+ long committedMillis) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.isStaged = true;
+
+ InstallSource installSource = InstallSource.create("testInstallInitiator",
+ "testInstallOriginator", "testInstaller", "testAttributionTag");
+
+ PackageInstallerSession session = new PackageInstallerSession(
+ /* callback */ null,
+ /* context */ null,
+ /* pm */ null,
+ /* sessionProvider */ null,
+ /* looper */ BackgroundThread.getHandler().getLooper(),
+ /* stagingManager */ null,
+ /* sessionId */ sessionId,
+ /* userId */ 456,
+ /* installerUid */ -1,
+ /* installSource */ installSource,
+ /* sessionParams */ params,
+ /* createdMillis */ 0L,
+ /* committedMillis */ committedMillis,
+ /* stageDir */ mTmpDir,
+ /* stageCid */ null,
+ /* files */ null,
+ /* checksums */ null,
+ /* prepared */ true,
+ /* committed */ true,
+ /* destroyed */ false,
+ /* sealed */ false, // Setting to true would trigger some PM logic.
+ /* childSessionIds */ null,
+ /* parentSessionId */ -1,
+ /* isReady */ false,
+ /* isFailed */ false,
+ /* isApplied */false,
+ /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
+ /* stagedSessionErrorMessage */ "no error");
+
+ StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
+ doReturn(packageName).when(stagedSession).getPackageName();
+ doAnswer(invocation -> {
+ Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
+ return filter.test(stagedSession);
+ }).when(stagedSession).sessionContains(any());
+ return stagedSession;
+ }
+
+ private static final class FakeStagedSession implements StagingManager.StagedSession {
+ private final int mSessionId;
+ private boolean mIsApex = false;
+ private boolean mIsCommitted = false;
+ private boolean mIsReady = false;
+ private boolean mIsApplied = false;
+ private boolean mIsFailed = false;
+ private @StagedSessionErrorCode int mErrorCode = -1;
+ private String mErrorMessage;
+ private boolean mIsDestroyed = false;
+ private int mParentSessionId = -1;
+ private String mPackageName;
+ private boolean mIsAbandonded = false;
+ private boolean mPreRebootVerificationStarted = false;
+ private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
+
+ private FakeStagedSession(int sessionId) {
+ mSessionId = sessionId;
+ }
+
+ private void setParentSessionId(int parentSessionId) {
+ mParentSessionId = parentSessionId;
+ }
+
+ private void setCommitted(boolean isCommitted) {
+ mIsCommitted = isCommitted;
+ }
+
+ private void setIsApex(boolean isApex) {
+ mIsApex = isApex;
+ }
+
+ private void setDestroyed(boolean isDestroyed) {
+ mIsDestroyed = isDestroyed;
+ }
+
+ private void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+
+ private boolean isAbandonded() {
+ return mIsAbandonded;
+ }
+
+ private boolean hasPreRebootVerificationStarted() {
+ return mPreRebootVerificationStarted;
+ }
+
+ private FakeStagedSession addChildSession(FakeStagedSession session) {
+ mChildSessions.add(session);
+ session.setParentSessionId(sessionId());
+ return this;
+ }
+
+ private @StagedSessionErrorCode int getErrorCode() {
+ return mErrorCode;
+ }
+
+ private String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ @Override
+ public boolean isMultiPackage() {
+ return !mChildSessions.isEmpty();
+ }
+
+ @Override
+ public boolean isApexSession() {
+ return mIsApex;
+ }
+
+ @Override
+ public boolean isCommitted() {
+ return mIsCommitted;
+ }
+
+ @Override
+ public boolean isInTerminalState() {
+ return isSessionApplied() || isSessionFailed();
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mIsDestroyed;
+ }
+
+ @Override
+ public boolean isSessionReady() {
+ return mIsReady;
+ }
+
+ @Override
+ public boolean isSessionApplied() {
+ return mIsApplied;
+ }
+
+ @Override
+ public boolean isSessionFailed() {
+ return mIsFailed;
+ }
+
+ @Override
+ public List<StagingManager.StagedSession> getChildSessions() {
+ return mChildSessions;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public int getParentSessionId() {
+ return mParentSessionId;
+ }
+
+ @Override
+ public int sessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public PackageInstaller.SessionParams sessionParams() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) {
+ return filter.test(this);
+ }
+
+ @Override
+ public boolean containsApkSession() {
+ Preconditions.checkState(!hasParentSessionId(), "Child session");
+ if (!isMultiPackage()) {
+ return !isApexSession();
+ }
+ for (StagingManager.StagedSession session : mChildSessions) {
+ if (!session.isApexSession()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean containsApexSession() {
+ Preconditions.checkState(!hasParentSessionId(), "Child session");
+ if (!isMultiPackage()) {
+ return isApexSession();
+ }
+ for (StagingManager.StagedSession session : mChildSessions) {
+ if (session.isApexSession()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setSessionReady() {
+ mIsReady = true;
+ }
+
+ @Override
+ public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
+ Preconditions.checkState(!mIsApplied, "Already marked as applied");
+ mIsFailed = true;
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ }
+
+ @Override
+ public void setSessionApplied() {
+ Preconditions.checkState(!mIsFailed, "Already marked as failed");
+ mIsApplied = true;
+ }
+
+ @Override
+ public void installSession(IntentSender statusReceiver) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasParentSessionId() {
+ return mParentSessionId != -1;
+ }
+
+ @Override
+ public long getCommittedMillis() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void abandon() {
+ mIsAbandonded = true;
+ }
+
+ @Override
+ public boolean notifyStartPreRebootVerification() {
+ mPreRebootVerificationStarted = true;
+ // TODO(ioffe): change to true when tests for pre-reboot verification are added.
+ return false;
+ }
+
+ @Override
+ public void notifyEndPreRebootVerification() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void verifySession() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 6561778..f1402ea 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -3,5 +3,4 @@
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
-per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
deleted file mode 100644
index 633957a..0000000
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ /dev/null
@@ -1,757 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.vibrator.IVibrator;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibratorService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorServiceTest
- */
-@Presubmit
-public class VibratorServiceTest {
-
- private static final int TEST_TIMEOUT_MILLIS = 1_000;
- private static final int UID = Process.ROOT_UID;
- private static final int VIBRATOR_ID = 1;
- private static final String PACKAGE_NAME = "package";
- private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
- private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
- .setBatterySaverEnabled(true).build();
- private static final VibrationAttributes ALARM_ATTRS =
- new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
- private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_TOUCH).build();
- private static final VibrationAttributes NOTIFICATION_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_NOTIFICATION).build();
- private static final VibrationAttributes RINGTONE_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_RINGTONE).build();
-
- @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
- @Mock private PackageManagerInternal mPackageManagerInternalMock;
- @Mock private PowerManagerInternal mPowerManagerInternalMock;
- @Mock private AppOpsManager mAppOpsManagerMock;
- @Mock private IVibratorStateListener mVibratorStateListenerMock;
- @Mock private IInputManager mIInputManagerMock;
- @Mock private IBinder mVibratorStateListenerBinderMock;
-
- private TestLooper mTestLooper;
- private ContextWrapper mContextSpy;
- private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
- private FakeVibrator mFakeVibrator;
- private FakeVibratorControllerProvider mVibratorProvider;
-
- @Before
- public void setUp() throws Exception {
- mTestLooper = new TestLooper();
- mFakeVibrator = new FakeVibrator();
- mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper());
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
-
- ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
- when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
- when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
- when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
- when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
- when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
- when(mPackageManagerInternalMock.getSystemUiServiceComponent())
- .thenReturn(new ComponentName("", ""));
- doAnswer(invocation -> {
- mRegisteredPowerModeListener = invocation.getArgument(0);
- return null;
- }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
- addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
- addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
- }
-
- @After
- public void tearDown() throws Exception {
- InputManager.clearInstance();
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.removeServiceForTest(PowerManagerInternal.class);
- }
-
- private VibratorService createService() {
- VibratorService service = new VibratorService(mContextSpy,
- new VibratorService.Injector() {
- @Override
- VibratorController createVibratorController(
- VibratorController.OnVibrationCompleteListener listener) {
- return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener);
- }
-
- @Override
- Handler createHandler(Looper looper) {
- return new Handler(mTestLooper.getLooper());
- }
-
- @Override
- void addService(String name, IBinder service) {
- // ignore
- }
- });
- service.systemReady();
- return service;
- }
-
- @Test
- public void createService_initializesNativeService() {
- createService();
- assertTrue(mVibratorProvider.isInitialized());
- }
-
- @Test
- public void hasVibrator_withVibratorHalPresent_returnsTrue() {
- assertTrue(createService().hasVibrator());
- }
-
- @Test
- public void hasVibrator_withNoVibratorHalPresent_returnsFalse() {
- mVibratorProvider.disableVibrators();
- assertFalse(createService().hasVibrator());
- }
-
- @Test
- public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- assertTrue(createService().hasAmplitudeControl());
- }
-
- @Test
- public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
- assertFalse(createService().hasAmplitudeControl());
- }
-
- @Test
- public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- assertTrue(createService().hasAmplitudeControl());
- }
-
- @Test
- public void getVibratorInfo_returnsSameInfoFromNative() {
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
- IVibrator.CAP_AMPLITUDE_CONTROL);
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-
- VibratorInfo info = createService().getVibratorInfo();
- assertTrue(info.hasAmplitudeControl());
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_TICK));
- assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
- }
-
- @Test
- public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
- setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
- vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
- vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
- vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(2, effects.size());
- assertEquals(10, effects.get(0).getDuration());
- assertEquals(100, effects.get(1).getDuration());
- }
-
- @Test
- public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception {
- VibratorService service = createService();
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
- vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
- vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(3, effects.size());
- assertEquals(2, effects.get(0).getDuration());
- assertEquals(3, effects.get(1).getDuration());
- assertEquals(4, effects.get(2).getDuration());
- }
-
- @Test
- public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
- VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
- audioAttributes, effect).build();
-
- vibrate(service, effect, vibrationAttributes);
-
- verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
- }
-
- @Test
- public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_UNKNOWN).build());
-
- InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
- anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
- }
-
- @Test
- public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude()
- throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(1, effects.size());
- assertEquals(100, effects.get(0).getDuration());
- assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes());
- }
-
- @Test
- public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude()
- throws Exception {
- VibratorService service = createService();
- clearInvocations();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(1, effects.size());
- assertEquals(100, effects.get(0).getDuration());
- assertTrue(mVibratorProvider.getAmplitudes().isEmpty());
- }
-
- @Test
- public void vibrate_withPrebaked_performsEffect() throws Exception {
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- vibrateAndWait(service, effect, ALARM_ATTRS);
-
- VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
- VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
- throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), any(), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception {
- VibratorService service = createService();
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception {
- VibratorService service = createService();
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- // Settings callback is async, so wait before checking it never got cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception {
- VibratorService service = createService();
- vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
- // FakeSettingsProvider don't support testing triggering ContentObserver yet.
- service.updateVibrators();
-
- // Settings callback is async, so wait before checking it never got cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withComposed_performsEffect() throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- vibrateAndWait(service, effect, ALARM_ATTRS);
- assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- vibrate(service, effect, ALARM_ATTRS);
- InOrder inOrderVerifier = inOrder(mIInputManagerMock);
- inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
- inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
- throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
- vibrateAndWait(service, effect, ALARM_ATTRS);
-
- assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes());
- assertEquals(
- Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)),
- mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- // Trigger callbacks from controller.
- mTestLooper.moveTimeForward(50);
- mTestLooper.dispatchAll();
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception {
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- service.cancelVibrate(service);
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
- VibratorService service = createService();
- service.registerVibratorStateListener(mVibratorStateListenerMock);
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-
- InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
- // First notification done when listener is registered.
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verifyNoMoreInteractions();
- }
-
- @Test
- public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
- VibratorService service = createService();
-
- service.registerVibratorStateListener(mVibratorStateListenerMock);
-
- vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- service.unregisterVibratorStateListener(mVibratorStateListenerMock);
- // Trigger callbacks from controller.
- mTestLooper.moveTimeForward(50);
- mTestLooper.dispatchAll();
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
- // First notification done when listener is registered.
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
- inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister
- inOrderVerifier.verifyNoMoreInteractions();
- }
-
- @Test
- public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception {
- // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- mVibratorProvider.setSupportedEffects(
- VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_TICK,
- VibrationEffect.EFFECT_DOUBLE_CLICK,
- VibrationEffect.EFFECT_HEAVY_CLICK);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
- NOTIFICATION_ATTRS);
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
- List<Integer> playedStrengths = mVibratorProvider.getEffects().stream()
- .map(VibrationEffect.Prebaked.class::cast)
- .map(VibrationEffect.Prebaked::getEffectStrength)
- .collect(Collectors.toList());
- assertEquals(Arrays.asList(
- VibrationEffect.EFFECT_STRENGTH_STRONG,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM,
- VibrationEffect.EFFECT_STRENGTH_LIGHT),
- playedStrengths);
- }
-
- @Test
- public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception {
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
-
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS);
- vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS);
- vibrateAndWait(service,
- VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
- HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS);
-
- List<Integer> amplitudes = mVibratorProvider.getAmplitudes();
- assertEquals(3, amplitudes.size());
- // Alarm vibration is never scaled.
- assertEquals(100, amplitudes.get(0).intValue());
- // Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertTrue(amplitudes.get(1) > 150);
- // Haptic feedback vibrations will be scaled with SCALE_LOW.
- assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50);
- }
-
- @Test
- public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception {
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
-
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
- .compose();
-
- vibrateAndWait(service, effect, ALARM_ATTRS);
- vibrateAndWait(service, effect, NOTIFICATION_ATTRS);
- vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, effect, RINGTONE_ATTRS);
-
- List<VibrationEffect.Composition.PrimitiveEffect> primitives =
- mVibratorProvider.getEffects().stream()
- .map(VibrationEffect.Composed.class::cast)
- .map(VibrationEffect.Composed::getPrimitiveEffects)
- .flatMap(List::stream)
- .collect(Collectors.toList());
-
- // Ringtone vibration is off, so only the other 3 are propagated to native.
- assertEquals(6, primitives.size());
-
- // Alarm vibration is never scaled.
- assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2);
- assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2);
-
- // Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2);
- assertTrue(0.7 < primitives.get(3).scale);
-
- // Haptic feedback vibrations will be scaled with SCALE_LOW.
- assertTrue(0.5 < primitives.get(4).scale);
- assertTrue(0.5 > primitives.get(5).scale);
- }
-
- private void vibrate(VibratorService service, VibrationEffect effect,
- VibrationAttributes attrs) {
- service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
- }
-
- private void vibrateAndWait(VibratorService service, VibrationEffect effect,
- VibrationAttributes attrs) throws Exception {
- CountDownLatch startedCount = new CountDownLatch(1);
- CountDownLatch finishedCount = new CountDownLatch(1);
- service.registerVibratorStateListener(new IVibratorStateListener() {
- @Override
- public void onVibrating(boolean vibrating) {
- if (vibrating) {
- startedCount.countDown();
- } else if (startedCount.getCount() == 0) {
- finishedCount.countDown();
- }
- }
-
- @Override
- public IBinder asBinder() {
- return mock(IBinder.class);
- }
- });
-
- mTestLooper.startAutoDispatch();
- service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
- assertTrue(startedCount.await(1, TimeUnit.SECONDS));
- assertTrue(finishedCount.await(1, TimeUnit.SECONDS));
- mTestLooper.stopAutoDispatchAndIgnoreExceptions();
- }
-
- private InputDevice createInputDeviceWithVibrator(int id) {
- return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
- false /* hasBattery */);
- }
-
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
-
- private void setRingerMode(int ringerMode) {
- AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
- audioManager.setRingerModeInternal(ringerMode);
- assertEquals(ringerMode, audioManager.getRingerModeInternal());
- }
-
- private void setUserSetting(String settingName, int value) {
- Settings.System.putIntForUser(
- mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
- }
-
- private void setGlobalSetting(String settingName, int value) {
- Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
- }
-
- private boolean waitUntil(Predicate<VibratorService> predicate,
- VibratorService service, long timeout) throws InterruptedException {
- long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
- boolean predicateResult = false;
- while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
- Thread.sleep(10);
- predicateResult = predicate.test(service);
- }
- return predicateResult;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
@@ -30,15 +28,12 @@
import android.hardware.power.stats.StateResidencyResult;
import android.power.PowerStatsInternal;
import android.util.SparseArray;
-import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
import org.junit.Before;
-import org.junit.Test;
import java.util.concurrent.CompletableFuture;
@@ -63,44 +58,6 @@
mBatteryStatsImpl);
}
- @Test
- public void getEnergyConsumptionData() {
- SparseLongArray expectSubsystems = new SparseLongArray();
- // Add some energy consumers used by BatteryExternalStatsWorker.
- final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
- "display");
- mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
- expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
- // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
- // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
- final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
- mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
- // Inform BESW that PowerStatsInternal is ready to query
- mBatteryExternalStatsWorker.systemServicesReady();
-
- MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
- assertEquals(expectSubsystems.size(), energies.size());
- final int size = expectSubsystems.size();
-
- for (int i = 0; i < size; i++) {
- int subsystem = expectSubsystems.keyAt(i);
- // find the subsystem in the returned MeasuredEnergyArray
- int subsystemIndex = -1;
- for (int j = 0; j < size; j++) {
- if (subsystem == energies.getSubsystem(i)) {
- subsystemIndex = i;
- break;
- }
- }
- assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
- subsystemIndex);
- assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
- }
- }
-
public class TestInjector extends BatteryExternalStatsWorker.Injector {
public TestInjector(Context context) {
super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
package com.android.server.am;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import androidx.test.filters.SmallTest;
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
-import org.junit.Before;
import org.junit.Test;
/**
@@ -38,134 +43,198 @@
*/
@SmallTest
public final class MeasuredEnergySnapshotTest {
- private static final int NUMBER_SUBSYSTEMS = 3;
- private static final int SUBSYSTEM_DISPLAY = 0;
- private static final int SUBSYSTEM_NEVER_USED = 1;
- private static final int SUBSYSTEM_CATAPULT = 2;
+ private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+ 0, 0, EnergyConsumerType.DISPLAY, "Display");
+ private static final EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+ 47, 0, EnergyConsumerType.OTHER, "GPU");
+ private static final EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+ 1, 1, EnergyConsumerType.OTHER, "HPU");
+ private static final EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+ 436, 2, EnergyConsumerType.OTHER, "IPU");
- private MeasuredEnergySnapshot mSnapshot;
+ private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+ CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+ private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+ CONSUMER_DISPLAY);
- // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
- private final int[] mAllSubsystems =
- {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
- // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
- private final int[] mSubsystemIndices = {0, 2, 1};
- private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
- private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return mAllSubsystems[index];
- }
-
- @Override
- public long getEnergy(int index) {
- return mCurrentSubsystemEnergyUJ[index];
- }
-
- @Override
- public int size() {
- return mAllSubsystems.length;
- }
+ // Elements in each results are purposefully out of order.
+ private static final EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+ // No CONSUMER_OTHER_2
};
- private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return mAllSubsystems[0];
- }
-
- @Override
- public long getEnergy(int index) {
- return mCurrentSubsystemEnergyUJ[0];
- }
-
- @Override
- public int size() {
- return 1;
- }
+ private static final EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+ createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+ };
+ private static final EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+ // No CONSUMER_OTHER_0
+ // No CONSUMER_OTHER_1
+ // No CONSUMER_OTHER_2
+ };
+ private static final EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+ // No CONSUMER_DISPLAY
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+ createEnergyConsumerResult(
+ CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+ };
+ private static final EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+ createEnergyConsumerResult(
+ CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+ // No CONSUMER_OTHER_1
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
};
- @Before
- public void setUp() {
- mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+ @Test
+ public void testUpdateAndGetDelta_empty() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+ assertNull(snapshot.updateAndGetDelta(null));
+ assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
}
@Test
public void testUpdateAndGetDelta() {
- SparseLongArray result;
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
- // Increment DISPLAY by 15
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+ // results0
+ MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+ if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ }
- // Increment DISPLAY by 7
- // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
- result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
- assertEquals(1, result.size());
- assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+ // results1
+ delta = snapshot.updateAndGetDelta(RESULTS_1);
+ assertNotNull(delta);
+ assertEquals(24 - 14, delta.displayEnergyUJ);
- // Increment CATAPULT by 64 (in addition to the previous increase of 5)
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+ assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+ assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
- // Do nothing
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals("0 results should not appear at all", 0, result.size());
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
- // Increment DISPLAY by 42
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+ // results2
+ delta = snapshot.updateAndGetDelta(RESULTS_2);
+ assertNotNull(delta);
+ assertEquals(36 - 24, delta.displayEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ assertNull(delta.otherTotalEnergyUJ);
- // Increment DISPLAY by 106 and CATAPULT by 13
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(2, result.size());
- assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
- assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+ // results3
+ delta = snapshot.updateAndGetDelta(RESULTS_3);
+ assertNotNull(delta);
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+ assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+ assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+ assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+ assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+ assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+ // results4
+ delta = snapshot.updateAndGetDelta(RESULTS_4);
+ assertNotNull(delta);
+ assertEquals(43 - 36, delta.displayEnergyUJ);
+
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+ assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+ assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+ assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+ assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+ assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
}
- private void incrementEnergyOfSubsystem(int subsystem, long energy) {
- mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+ /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+ @Test
+ public void testUpdateAndGetDelta_some() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+ // results0
+ MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+ if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ }
+
+ // results1
+ delta = snapshot.updateAndGetDelta(RESULTS_1);
+ assertNotNull(delta);
+ assertEquals(24 - 14, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+ assertNull(delta.otherUidEnergiesUJ);
}
@Test
- public void testUpdateAndGetDelta_null() {
- assertNull(mSnapshot.updateAndGetDelta(null));
+ public void testGetNumOtherOrdinals() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+ assertEquals(3, snapshot.getNumOtherOrdinals());
}
@Test
- public void testHasSubsystem() {
- // Setup MeasuredEnergySnapshot which reported some of the subsystems.
- final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
- MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return subsystems[index];
- }
+ public void testGetNumOtherOrdinals_none() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+ assertEquals(0, snapshot.getNumOtherOrdinals());
+ }
- @Override
- public long getEnergy(int index) {
- return 0; // Irrelevant for this test.
- }
+ private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+ final EnergyConsumer ec = new EnergyConsumer();
+ ec.id = id;
+ ec.ordinal = ord;
+ ec.type = type;
+ ec.name = name;
+ return ec;
+ }
- @Override
- public int size() {
- return subsystems.length;
- }
- };
- final MeasuredEnergySnapshot snapshot =
- new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+ private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+ final SparseArray<EnergyConsumer> map = new SparseArray<>();
+ for (EnergyConsumer ec : ecs) {
+ map.put(ec.id, ec);
+ }
+ return map;
+ }
- assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
- assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
- assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+ private static EnergyConsumerResult createEnergyConsumerResult(
+ int id, long energyUWs, int[] uids, long[] uidEnergies) {
+ final EnergyConsumerResult ecr = new EnergyConsumerResult();
+ ecr.id = id;
+ ecr.energyUWs = energyUWs;
+ if (uids != null) {
+ ecr.attribution = new EnergyConsumerAttribution[uids.length];
+ for (int i = 0; i < uids.length; i++) {
+ ecr.attribution[i] = new EnergyConsumerAttribution();
+ ecr.attribution[i].uid = uids[i];
+ ecr.attribution[i].energyUWs = uidEnergies[i];
+ }
+ }
+ return ecr;
+ }
+
+ private void assertNullOrEmpty(SparseLongArray a) {
+ if (a != null) assertEquals("Array should be null or empty", 0, a.size());
}
}
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/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/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 1a22661..a078a77 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -52,7 +52,8 @@
public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
- private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
+ // A device state that is not reported as being supported for the default test provider.
+ private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
new file mode 100644
index 0000000..b5c8053
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceState}.
+ * <p/>
+ * Run with <code>atest DeviceStateTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateTest {
+ @Test
+ public void testConstruct() {
+ final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
+ "CLOSED" /* name */);
+ assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
+ assertEquals(state.getName(), "CLOSED");
+ }
+
+ @Test
+ public void testConstruct_nullName() {
+ final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
+ null /* name */);
+ assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
+ assertNull(state.getName());
+ }
+
+ @Test
+ public void testConstruct_tooLargeIdentifier() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
+ null /* name */);
+ });
+ }
+
+ @Test
+ public void testConstruct_tooSmallIdentifier() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
+ null /* name */);
+ });
+ }
+}
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/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 640d6e5..1c55072 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -45,7 +45,9 @@
import android.hardware.input.InputManagerInternal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.MessageQueue;
import android.os.Process;
+import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -76,10 +78,15 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.time.Duration;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.stream.LongStream;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class DisplayManagerServiceTest {
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -193,8 +200,8 @@
verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
List<DisplayViewport> viewports = viewportCaptor.getValue();
- // Expect to receive 2 viewports: internal, and virtual
- assertEquals(2, viewports.size());
+ // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+ assertTrue(viewports.size() >= 2);
DisplayViewport virtualViewport = null;
DisplayViewport internalViewport = null;
@@ -202,7 +209,10 @@
DisplayViewport v = viewports.get(i);
switch (v.type) {
case DisplayViewport.VIEWPORT_INTERNAL: {
+ // If more than one internal viewport, this will get overwritten several times,
+ // which for the purposes of this test is fine.
internalViewport = v;
+ assertTrue(internalViewport.valid);
break;
}
case DisplayViewport.VIEWPORT_EXTERNAL: {
@@ -219,9 +229,6 @@
assertNotNull(internalViewport);
assertNotNull(virtualViewport);
- // INTERNAL
- assertTrue(internalViewport.valid);
-
// VIRTUAL
assertEquals(height, virtualViewport.deviceHeight);
assertEquals(width, virtualViewport.deviceWidth);
@@ -243,10 +250,12 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
final int displayIds[] = bs.getDisplayIds();
- assertEquals(1, displayIds.length);
- final int displayId = displayIds[0];
- DisplayInfo info = bs.getDisplayInfo(displayId);
- assertEquals(info.type, Display.TYPE_INTERNAL);
+ final int size = displayIds.length;
+ assertTrue(size > 0);
+ for (int i = 0; i < size; i++) {
+ DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+ assertEquals(info.type, Display.TYPE_INTERNAL);
+ }
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -257,16 +266,22 @@
verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
List<DisplayViewport> viewports = viewportCaptor.getValue();
- // Expect to receive actual viewports: 1 internal
- assertEquals(1, viewports.size());
+ // Due to the nature of foldables, we may have a different number of viewports than
+ // displays, just verify there's at least one.
+ final int viewportSize = viewports.size();
+ assertTrue(viewportSize > 0);
- DisplayViewport internalViewport = viewports.get(0);
+ // Now verify that each viewport's displayId is valid.
+ Arrays.sort(displayIds);
+ for (int i = 0; i < viewportSize; i++) {
+ DisplayViewport internalViewport = viewports.get(i);
- // INTERNAL is the only one actual display.
- assertNotNull(internalViewport);
- assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
- assertTrue(internalViewport.valid);
- assertEquals(displayId, internalViewport.displayId);
+ // INTERNAL is the only one actual display.
+ assertNotNull(internalViewport);
+ assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
+ assertTrue(internalViewport.valid);
+ assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0);
+ }
}
@Test
@@ -486,7 +501,6 @@
* Tests that collection of display color sampling results are sensible.
*/
@Test
- @FlakyTest(bugId = 172555744)
public void testDisplayedContentSampling() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
@@ -937,8 +951,22 @@
// Would prefer to call displayManager.onStart() directly here but it performs binderService
// registration which triggers security exceptions when running from a test.
handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
- // flush the handler
- handler.runWithScissors(() -> {}, 0 /* now */);
+ waitForIdleHandler(handler, Duration.ofSeconds(1));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
+ }
}
private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
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/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 0f527f3..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,8 +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;
@@ -27,6 +30,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
@@ -66,6 +70,7 @@
private IPowerManager mIPowerManagerMock;
@Mock
private IThermalService mIThermalServiceMock;
+ @Mock private AudioManager mAudioManager;
@Before
public void setUp() {
@@ -101,11 +106,21 @@
}
@Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
protected PowerManager getPowerManager() {
return powerManager;
}
@Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
protected HdmiCecConfig getHdmiCecConfig() {
return hdmiCecConfig;
}
@@ -121,9 +136,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
- HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ hdmiPortInfos[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -302,4 +319,185 @@
assertThat(features.contains(Constants.RC_PROFILE_TV_THREE)).isFalse();
assertThat(features.contains(Constants.RC_PROFILE_TV_FOUR)).isFalse();
}
+
+ @Test
+ public void startArcAction_enable_noAudioDevice() {
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+
+ @Test
+ public void startArcAction_disable_noAudioDevice() {
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_enable_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_disable_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_enable_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_disable_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
+ @Test
+ public void handleInitiateArc_noAudioDevice() {
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ }
+
+ @Test
+ public void handleInitiateArc_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ }
+
+ @Test
+ public void handleInitiateArc_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ 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/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
new file mode 100644
index 0000000..b8dfd56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ActiveSourceAction} */
+@SmallTest
+@RunWith(JUnit4.class)
+public class PowerStatusMonitorActionTest {
+
+ private Context mContextSpy;
+ private HdmiControlService mHdmiControlService;
+ private FakeNativeWrapper mNativeWrapper;
+
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPhysicalAddress;
+ private HdmiCecLocalDeviceTv mTvDevice;
+
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalService mIThermalServiceMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+ when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+
+ mHdmiControlService = new HdmiControlService(mContextSpy) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public void setWiredDeviceConnectionState(
+ int type, int state, String address, String name) {
+ // Do nothing.
+ }
+ };
+ }
+
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
+ protected PowerManager getPowerManager() {
+ return powerManager;
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+
+ @Override
+ protected HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
+ };
+
+ Looper looper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(looper);
+ mNativeWrapper = new FakeNativeWrapper();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
+ mTvDevice.init();
+ mLocalDevices.add(mTvDevice);
+ mTestLooper.dispatchAll();
+ HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
+ hdmiPortInfo[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ hdmiPortInfo[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfo);
+ mHdmiControlService.initService();
+ mPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void sourceDevice_1_4_updatesPowerState() {
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON);
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+
+ mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY);
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY);
+ }
+
+ private void assertPowerStatus(int logicalAddress, int powerStatus) {
+ HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(
+ logicalAddress);
+ assertThat(deviceInfo).isNotNull();
+ assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void sourceDevice_2_0_doesNotUpdatePowerState() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+ mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+ mTestLooper.dispatchAll();
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+ }
+
+ @Test
+ public void mixedSourceDevices_localDevice_1_4_updatesAll() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ mTestLooper.dispatchAll();
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+ reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
+ }
+
+ @Test
+ public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mTestLooper.dispatchAll();
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+ reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_2);
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
+ }
+
+ private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) {
+ HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mNativeWrapper.onCecMessage(playbackDevice);
+ mTestLooper.dispatchAll();
+ }
+
+ private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
+ int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ logicalAddress, destination,
+ powerStatus);
+ mNativeWrapper.onCecMessage(reportPowerStatus);
+ mTestLooper.dispatchAll();
+ }
+}
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 353ac4b..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;
@@ -39,6 +40,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -51,7 +53,7 @@
private static final String TAG = "WorkerCountTrackerTest";
private static final double[] EQUAL_PROBABILITY_CDF =
- buildCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+ buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
1.0 / NUM_WORK_TYPES);
private Random mRandom;
@@ -64,18 +66,19 @@
}
@NonNull
- private static double[] buildCdf(double pTop, double pEj, double pBg, double pBgUser) {
- double[] cdf = new double[JobConcurrencyManager.NUM_WORK_TYPES];
+ private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) {
+ return buildCdf(pTop, pEj, pBg, pBgUser);
+ }
+
+ @NonNull
+ private static double[] buildCdf(double... probs) {
+ double[] cdf = new double[probs.length];
double sum = 0;
- sum += pTop;
- cdf[0] = sum;
- sum += pEj;
- cdf[1] = sum;
- sum += pBg;
- cdf[2] = sum;
- sum += pBgUser;
- cdf[3] = sum;
+ for (int i = 0; i < probs.length; ++i) {
+ sum += probs[i];
+ cdf[i] = sum;
+ }
if (Double.compare(1, sum) != 0) {
throw new IllegalArgumentException("probabilities don't sum to one: " + sum);
@@ -83,25 +86,30 @@
return cdf;
}
- @JobConcurrencyManager.WorkType
- static int getRandomWorkType(double[] cdf, double rand) {
+ static int getRandomIndex(double[] cdf, double rand) {
for (int i = cdf.length - 1; i >= 0; --i) {
if (rand < cdf[i] && (i == 0 || rand > cdf[i - 1])) {
- switch (i) {
- case 0:
- return WORK_TYPE_TOP;
- case 1:
- return WORK_TYPE_EJ;
- case 2:
- return WORK_TYPE_BG;
- case 3:
- return WORK_TYPE_BGUSER;
- default:
- throw new IllegalStateException("Unknown work type");
- }
+ return i;
}
}
- throw new IllegalStateException("Couldn't pick random work type");
+ throw new IllegalStateException("Couldn't pick random index");
+ }
+
+ @JobConcurrencyManager.WorkType
+ static int getRandomWorkType(double[] cdf, double rand) {
+ final int index = getRandomIndex(cdf, rand);
+ switch (index) {
+ case 0:
+ return WORK_TYPE_TOP;
+ case 1:
+ return WORK_TYPE_EJ;
+ case 2:
+ return WORK_TYPE_BG;
+ case 3:
+ return WORK_TYPE_BGUSER;
+ default:
+ throw new IllegalStateException("Unknown work type");
+ }
}
/**
@@ -110,25 +118,59 @@
class Jobs {
public final SparseIntArray running = new SparseIntArray();
public final SparseIntArray pending = new SparseIntArray();
+ public final List<Integer> pendingMultiTypes = new ArrayList<>();
- public void maybeEnqueueJobs(double probStart, double[] typeCdf) {
+ /**
+ * @param probStart Probability of starting a job
+ * @param typeCdf The CDF representing the probability of each work type
+ * @param numTypesCdf The CDF representing the probability of a job having X different
+ * work types. Each index i represents i+1 work types (ie. index 0 = 1
+ * work type, index 3 = 4 work types).
+ */
+ public void maybeEnqueueJobs(double probStart, double[] typeCdf, double[] numTypesCdf) {
+ assertThat(numTypesCdf.length).isAtMost(NUM_WORK_TYPES);
+ assertThat(numTypesCdf.length).isAtLeast(1);
+
while (mRandom.nextDouble() < probStart) {
- final int workType = getRandomWorkType(typeCdf, mRandom.nextDouble());
- pending.put(workType, pending.get(workType) + 1);
+ final int numTypes = getRandomIndex(numTypesCdf, mRandom.nextDouble()) + 1;
+ int types = WORK_TYPE_NONE;
+ for (int i = 0; i < numTypes; ++i) {
+ types |= getRandomWorkType(typeCdf, mRandom.nextDouble());
+ }
+ addPending(types, 1);
}
}
- public void maybeFinishJobs(double probStop) {
- for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
- if (mRandom.nextDouble() < probStop) {
- running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
- mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+ void addPending(int allWorkTypes, int num) {
+ for (int n = 0; n < num; ++n) {
+ for (int i = 0; i < 32; ++i) {
+ final int type = 1 << i;
+ if ((allWorkTypes & type) != 0) {
+ pending.put(type, pending.get(type) + 1);
+ }
+ }
+ pendingMultiTypes.add(allWorkTypes);
+ }
+ }
+
+ void removePending(int allWorkTypes) {
+ for (int i = 0; i < 32; ++i) {
+ final int type = 1 << i;
+ if ((allWorkTypes & type) != 0) {
+ pending.put(type, pending.get(type) - 1);
}
}
- for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
- if (mRandom.nextDouble() < probStop) {
- running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
- mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+ pendingMultiTypes.remove(Integer.valueOf(allWorkTypes));
+ }
+
+ public void maybeFinishJobs(double probStop) {
+ for (int i = running.size() - 1; i >= 0; --i) {
+ final int workType = running.keyAt(i);
+ for (int c = running.valueAt(i); c > 0; --c) {
+ if (mRandom.nextDouble() < probStop) {
+ running.put(workType, running.get(workType) - 1);
+ mWorkCountTracker.onJobFinished(workType);
+ }
}
}
}
@@ -171,6 +213,15 @@
return false;
}
+ private int getPendingMultiType(Jobs jobs, @JobConcurrencyManager.WorkType int workType) {
+ for (int multiType : jobs.pendingMultiTypes) {
+ if ((multiType & workType) != 0) {
+ return multiType;
+ }
+ }
+ throw new IllegalStateException("No pending multi type with work type: " + workType);
+ }
+
private void startPendingJobs(Jobs jobs) {
while (hasStartablePendingJob(jobs)) {
final int startingWorkType =
@@ -178,9 +229,10 @@
if (jobs.pending.get(startingWorkType) > 0
&& mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
- jobs.pending.put(startingWorkType, jobs.pending.get(startingWorkType) - 1);
+ final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+ jobs.removePending(pendingMultiType);
jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
- mWorkCountTracker.stageJob(startingWorkType);
+ mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
mWorkCountTracker.onJobStarted(startingWorkType);
}
}
@@ -192,12 +244,17 @@
private void checkRandom(Jobs jobs, int numTests, int totalMax,
@NonNull List<Pair<Integer, Integer>> minLimits,
@NonNull List<Pair<Integer, Integer>> maxLimits,
- double probStart, double[] typeCdf, double probStop) {
+ double probStart, double[] typeCdf, double[] numTypesCdf, double probStop) {
+ int minExpected = 0;
+ for (Pair<Integer, Integer> minLimit : minLimits) {
+ minExpected = Math.min(minLimit.second, minExpected);
+ }
for (int i = 0; i < numTests; i++) {
jobs.maybeFinishJobs(probStop);
- jobs.maybeEnqueueJobs(probStart, typeCdf);
+ jobs.maybeEnqueueJobs(probStart, typeCdf, numTypesCdf);
recount(jobs, totalMax, minLimits, maxLimits);
+ final int numPending = jobs.pendingMultiTypes.size();
startPendingJobs(jobs);
int totalRunning = 0;
@@ -209,6 +266,7 @@
totalRunning += numRunning;
}
assertThat(totalRunning).isAtMost(totalMax);
+ assertThat(totalRunning).isAtLeast(Math.min(minExpected, numPending));
for (Pair<Integer, Integer> maxLimit : maxLimits) {
assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
.that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
@@ -233,7 +291,7 @@
final double probStart = 0.1;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
- EQUAL_PROBABILITY_CDF, probStop);
+ EQUAL_PROBABILITY_CDF, EQUAL_PROBABILITY_CDF, probStop);
}
@Test
@@ -246,10 +304,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.5, 0, 0.5, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0);
+ final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -262,10 +322,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] numTypesCdf = buildCdf(.75, .2, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -278,10 +340,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double probStop = 0.5;
- final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] numTypesCdf = buildCdf(.05, .95);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -294,10 +358,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.1, 0, 0.8, .1);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1);
+ final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -310,10 +376,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.9, 0, 0.1, 0);
+ final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -326,10 +394,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.1, 0, 0.1, .8);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8);
+ final double[] numTypesCdf = buildCdf(0.5, 0.5);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -343,10 +413,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.9, 0, 0.05, 0.05);
+ final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -360,10 +432,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.5, 0.5);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -377,10 +451,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.1, 0.9);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9);
+ final double[] numTypesCdf = buildCdf(0.9, 0.1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -394,10 +470,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.9, 0.1);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -410,13 +488,16 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.5, 0.5, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0);
+ final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
+ @LargeTest
public void testRandom13() {
assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES);
@@ -429,11 +510,12 @@
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 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, probStop);
+ EQUAL_PROBABILITY_CDF, numTypesCdf, probStop);
}
@Test
@@ -446,10 +528,12 @@
List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(.1, 0.5, 0.35, 0.05);
+ final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -464,10 +548,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.01, 0.49, 0.1, 0.4);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4);
+ final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
/** Used by the following tests */
@@ -483,7 +569,7 @@
jobs.running.put(run.first, run.second);
}
for (Pair<Integer, Integer> pend : pending) {
- jobs.pending.put(pend.first, pend.second);
+ jobs.addPending(pend.first, pend.second);
}
recount(jobs, totalMax, minLimits, maxLimits);
@@ -651,14 +737,32 @@
Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
/* resPen */ List.of(
Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
+
+ // Test multi-types
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* run */ List.of(),
+ /* pen */ List.of(
+ // 2 of these as TOP, 1 as EJ
+ Pair.create(WORK_TYPE_TOP | WORK_TYPE_EJ, 3),
+ // 1 as EJ, 2 as BG
+ Pair.create(WORK_TYPE_EJ | WORK_TYPE_BG, 3),
+ Pair.create(WORK_TYPE_BG, 4),
+ Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2),
+ Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)));
}
/** Tests that the counter updates properly when jobs are stopped. */
@Test
public void testJobLifecycleLoop() {
final Jobs jobs = new Jobs();
- jobs.pending.put(WORK_TYPE_TOP, 11);
- jobs.pending.put(WORK_TYPE_BG, 10);
+ jobs.addPending(WORK_TYPE_TOP, 11);
+ jobs.addPending(WORK_TYPE_BG, 10);
final int totalMax = 6;
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
@@ -729,4 +833,149 @@
assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
}
+
+ /** Tests that the counter updates properly when jobs are stopped. */
+ @Test
+ public void testJobLifecycleLoop_Multitype() {
+ final Jobs jobs = new Jobs();
+ jobs.addPending(WORK_TYPE_TOP, 6); // a
+ jobs.addPending(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+ jobs.addPending(WORK_TYPE_BG, 10); // c
+
+ final int totalMax = 8;
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+ recount(jobs, totalMax, minLimits, maxLimits);
+
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(5);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+ // Since starting happens in random order, all EJs could have run first.
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+ // Stop all jobs
+ jobs.maybeFinishJobs(1);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+ // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+ // for EJ, which would reduce BG count to 3 instead of 4.
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(5);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(6);
+
+ // Stop only a bg job and make sure the counter only allows another bg job to start.
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+ // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+ // for EJ, which would reduce BG count to 3 instead of 4.
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(4);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(5);
+ }
+
+ /** Tests that the counter updates properly when jobs are stopped. */
+ @Test
+ public void testJobLifecycleLoop_Multitype_RandomOrder() {
+ final Jobs jobs = new Jobs();
+ SparseIntArray multiToCount = new SparseIntArray();
+ multiToCount.put(WORK_TYPE_TOP, 6); // a
+ multiToCount.put(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+ multiToCount.put(WORK_TYPE_EJ | WORK_TYPE_BG, 5); // c
+ multiToCount.put(WORK_TYPE_BG, 5); // d
+ while (multiToCount.size() > 0) {
+ final int index = mRandom.nextInt(multiToCount.size());
+ final int count = multiToCount.valueAt(index);
+ jobs.addPending(multiToCount.keyAt(index), 1);
+ if (count <= 1) {
+ multiToCount.removeAt(index);
+ } else {
+ multiToCount.put(multiToCount.keyAt(index), count - 1);
+ }
+ }
+
+ final int totalMax = 8;
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+ recount(jobs, totalMax, minLimits, maxLimits);
+
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(10);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+ startPendingJobs(jobs);
+
+ // Random order, but we should have 6 TOP, 1 EJ, and 1 BG running.
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+ // Can't equate pending EJ since some could be running as TOP and BG
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+ // Stop all jobs
+ jobs.maybeFinishJobs(1);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ // Random order, but we should have 4 TOP, 1 EJ, and 1 BG running.
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ // At this point, all TOP should be running (or have already run).
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(5);
+
+ // Stop only a bg job and make sure the counter only allows another bg job to start.
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_NONE);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index 25dbc6b..33ea710 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -45,6 +45,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,8 @@
MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getTargetContext();
mUserId = ActivityManager.getCurrentUser();
- mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+ mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0,
+ Process.SHELL_UID);
when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
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/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index a4ba4c8..a896f1b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -43,6 +43,7 @@
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserManager;
@@ -155,6 +156,11 @@
}
@Override
+ void post(Handler handler, Runnable runnable) {
+ runnable.run();
+ }
+
+ @Override
public UserManager getUserManager() {
return mUserManager;
}
@@ -369,7 +375,7 @@
@Test
public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
}
@Test
@@ -401,7 +407,7 @@
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -435,7 +441,7 @@
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mServiceConnection).unwrap(any(), anyLong());
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -466,7 +472,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
verify(mInjected, never()).reportMetric(anyBoolean());
}
@@ -493,7 +499,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected, never()).reportMetric(anyBoolean());
}
@@ -527,7 +533,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected).reportMetric(eq(true));
}
@@ -557,7 +563,7 @@
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertFalse(metricsSuccessCaptor.getValue());
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
index bc1e025..28b737b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -42,7 +43,6 @@
import org.mockito.stubbing.Answer;
import java.io.File;
-import java.io.IOException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -130,7 +130,7 @@
@Test
public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
- doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+ doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong());
assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
deleted file mode 100644
index 79935c2..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-
-import com.android.internal.os.BackgroundThread;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.function.Predicate;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-@Presubmit
-@RunWith(JUnit4.class)
-public class StagingManagerTest {
- @Rule
- public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
- private File mTmpDir;
- private StagingManager mStagingManager;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
- StorageManager storageManager = Mockito.mock(StorageManager.class);
- Context context = Mockito.mock(Context.class);
- when(storageManager.isCheckpointSupported()).thenReturn(true);
- when(context.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
- when(context.getSystemService(eq(Context.STORAGE_SERVICE))).thenReturn(storageManager);
-
- mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
- mStagingManager = new StagingManager(context, null);
- }
-
- /**
- * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
- * check.
- */
- @Test
- public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
- throws Exception {
- // Create 2 sessions with overlapping packages
- StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
- StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
-
- mStagingManager.createSession(session1);
- mStagingManager.createSession(session2);
- // Session1 should not fail in spite of the overlapping packages
- mStagingManager.checkNonOverlappingWithStagedSessions(session1);
- // Session2 should fail due to overlapping packages
- assertThrows(PackageManagerException.class,
- () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
- }
-
- private StagingManager.StagedSession createSession(int sessionId, String packageName,
- long committedMillis) {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.isStaged = true;
-
- InstallSource installSource = InstallSource.create("testInstallInitiator",
- "testInstallOriginator", "testInstaller", "testAttributionTag");
-
- PackageInstallerSession session = new PackageInstallerSession(
- /* callback */ null,
- /* context */ null,
- /* pm */ null,
- /* sessionProvider */ null,
- /* looper */ BackgroundThread.getHandler().getLooper(),
- /* stagingManager */ null,
- /* sessionId */ sessionId,
- /* userId */ 456,
- /* installerUid */ -1,
- /* installSource */ installSource,
- /* sessionParams */ params,
- /* createdMillis */ 0L,
- /* committedMillis */ committedMillis,
- /* stageDir */ mTmpDir,
- /* stageCid */ null,
- /* files */ null,
- /* checksums */ null,
- /* prepared */ true,
- /* committed */ true,
- /* destroyed */ false,
- /* sealed */ false, // Setting to true would trigger some PM logic.
- /* childSessionIds */ null,
- /* parentSessionId */ -1,
- /* isReady */ false,
- /* isFailed */ false,
- /* isApplied */false,
- /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
- /* stagedSessionErrorMessage */ "no error");
-
- StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
- doReturn(packageName).when(stagedSession).getPackageName();
- doAnswer(invocation -> {
- Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
- return filter.test(stagedSession);
- }).when(stagedSession).sessionContains(any());
- return stagedSession;
- }
-}
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/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
int[] reasons = new int[] {
PackageManagerService.REASON_FIRST_BOOT,
- PackageManagerService.REASON_BOOT,
+ PackageManagerService.REASON_POST_BOOT,
PackageManagerService.REASON_INSTALL,
PackageManagerService.REASON_BACKGROUND_DEXOPT,
PackageManagerService.REASON_AB_OTA,
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/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index b28994c..5574836 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 6dcfb42..daa1b25 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -27,7 +27,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
index 72c40ea..014bfd2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
@@ -23,7 +23,7 @@
import androidx.annotation.NonNull;
/** Fake implementation of {@link Vibrator} for service tests. */
-public final class FakeVibrator extends Vibrator {
+final class FakeVibrator extends Vibrator {
private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index f562c16..4634e12 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -33,7 +33,7 @@
* Provides {@link VibratorController} with controlled vibrator hardware capabilities and
* interactions.
*/
-public final class FakeVibratorControllerProvider {
+final class FakeVibratorControllerProvider {
private static final int EFFECT_DURATION = 20;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index e71c2f5..8c62b7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -67,9 +67,8 @@
private static final String REASON = "some reason";
private static final VibrationAttributes VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
- private static final VibrationEffect EFFECT = VibrationEffect.createOneShot(100, 255);
private static final CombinedVibrationEffect SYNCED_EFFECT =
- CombinedVibrationEffect.createSynced(EFFECT);
+ CombinedVibrationEffect.createSynced(VibrationEffect.createOneShot(100, 255));
@Rule public MockitoRule rule = MockitoJUnit.rule();
@@ -105,6 +104,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.onInputDeviceAdded(1);
@@ -118,6 +118,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
updateInputDevices(new int[]{1});
@@ -132,6 +133,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
updateInputDevices(new int[]{1});
@@ -142,6 +144,7 @@
@Test
public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
@@ -153,6 +156,7 @@
@Test
public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
@@ -167,6 +171,7 @@
@Test
public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
@@ -181,6 +186,7 @@
@Test
public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
@@ -195,8 +201,10 @@
@Test
public void onInputDeviceRemoved_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
createInputDeviceWithoutVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -209,7 +217,9 @@
@Test
public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -223,6 +233,7 @@
public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -240,14 +251,16 @@
public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
- verify(mIInputManagerMock).vibrate(eq(1), same(EFFECT), any());
- verify(mIInputManagerMock).vibrate(eq(2), same(EFFECT), any());
+ verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
+ verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
}
@Test
@@ -261,7 +274,9 @@
public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 3ff8e76..1b7e1ca 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
@@ -709,6 +710,7 @@
assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
}
+ @LargeTest
@Test
public void vibrate_withWaveform_totalVibrationTimeRespected() {
int totalDuration = 10_000; // 10s
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f0d7006..ba0a472 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.vibrator;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -73,9 +73,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
@@ -198,6 +196,10 @@
return mVibratorProviders.get(vibratorId)
.newVibratorController(vibratorId, listener);
}
+
+ @Override
+ void addService(String name, IBinder service) {
+ }
});
service.systemReady();
return service;
@@ -529,28 +531,15 @@
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorManagerService service = createService();
- // Prebaked vibration will play fallback waveform on input device.
- ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
- assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
-
- VibrationEffect[] effects = new VibrationEffect[]{
- VibrationEffect.createOneShot(100, 128),
- VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .compose(),
- };
-
- for (VibrationEffect effect : effects) {
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
- }
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(10, 10));
+ vibrate(service, effect, ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
// VibrationThread will start this vibration async, so wait before checking it never played.
assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
new file mode 100644
index 0000000..aa87958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ec28baf..07475e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1686,6 +1686,11 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+
+ }
+
+ @Override
protected void loadDefaultsFromConfig() {
mDefaultComponents.addAll(mDefaults);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index be489c3..5614aa2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@
"disabledMessage", 0, "disabledMessageResName",
null, null, 0, null, 0, 0,
0, "iconResName", "bitmapPath", null, 0,
- null, null);
+ null, null, 0);
return si;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index afcf08ef..80a046a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,11 @@
*/
package com.android.server.notification;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -27,11 +32,14 @@
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
import android.service.notification.NotificationListenerFilter;
+import android.service.notification.NotificationListenerService;
import android.util.ArraySet;
import android.util.Pair;
+import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
@@ -47,8 +55,6 @@
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
public class NotificationListenersTest extends UiServiceTestCase {
@@ -80,22 +86,21 @@
@Test
public void testReadExtraTag() throws Exception {
- String xml = "<req_listeners>"
+ String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+ "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
+ "<allowed types=\"7\" />"
- + "<disallowed pkgs=\"\" />"
+ "</listener>"
+ "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">"
+ "<allowed types=\"4\" />"
- + "<disallowed pkgs=\"something\" />"
+ + "<disallowed pkg=\"pkg1\" uid=\"243\"/>"
+ "</listener>"
- + "</req_listeners>";
+ + "</" + TAG_REQUESTED_LISTENERS + ">";
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mListeners.readExtraTag("req_listeners", parser);
+ mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser);
validateListenersFromXml();
}
@@ -103,8 +108,9 @@
@Test
public void testWriteExtraTag() throws Exception {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -134,16 +140,18 @@
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
.isEqualTo(4);
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10))
.getDisallowedPackages())
- .contains("something");
+ .contains(a1);
}
@Test
public void testOnUserRemoved() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -155,58 +163,68 @@
}
@Test
- public void testOnUserUnlocked() {
- // one exists already, say from xml
- NotificationListenerFilter nlf =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
- mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
-
- // new service exists or backfilling on upgrade to S
+ public void testEnsureFilters_newServiceNoMetadata() {
ServiceInfo si = new ServiceInfo();
- si.permission = mListeners.getConfig().bindPermission;
+ si.packageName = "new2";
+ si.name = "comp2";
+
+ mListeners.ensureFilters(si, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull();
+ }
+
+ @Test
+ public void testEnsureFilters_preExisting() {
+ // one exists already, say from xml
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ NotificationListenerFilter nlf =
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
+ ServiceInfo siOld = new ServiceInfo();
+ siOld.packageName = mCn2.getPackageName();
+ siOld.name = mCn2.getClassName();
+
+ mListeners.ensureFilters(siOld, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf);
+ }
+
+ @Test
+ public void testEnsureFilters_newServiceWithMetadata() {
+ ServiceInfo si = new ServiceInfo();
si.packageName = "new";
si.name = "comp";
- ResolveInfo ri = new ResolveInfo();
- ri.serviceInfo = si;
+ si.metaData = new Bundle();
+ si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2");
- // incorrect service
- ServiceInfo si2 = new ServiceInfo();
- ResolveInfo ri2 = new ResolveInfo();
- ri2.serviceInfo = si2;
- si2.packageName = "new2";
- si2.name = "comp2";
-
- List<ResolveInfo> ris = new ArrayList<>();
- ris.add(ri);
- ris.add(ri2);
-
- when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
-
- mListeners.onUserUnlocked(0);
-
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
- .isEqualTo(4);
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
- .getDisallowedPackages())
- .contains("something");
+ mListeners.ensureFilters(si, 0);
assertThat(mListeners.getNotificationListenerFilter(
Pair.create(si.getComponentName(), 0)).getTypes())
- .isEqualTo(15);
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
- .getDisallowedPackages())
- .isEmpty();
+ .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING);
+ }
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
- .isNull();
+ @Test
+ public void testEnsureFilters_newServiceWithEmptyMetadata() {
+ ServiceInfo si = new ServiceInfo();
+ si.packageName = "new";
+ si.name = "comp";
+ si.metaData = new Bundle();
+ si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "");
+ mListeners.ensureFilters(si, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(
+ Pair.create(si.getComponentName(), 0)).getTypes())
+ .isEqualTo(0);
}
@Test
public void testOnPackageChanged() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -224,8 +242,9 @@
@Test
public void testOnPackageChanged_removing() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
@@ -239,4 +258,21 @@
.isEqualTo(4);
}
+ @Test
+ public void testOnPackageChanged_removingDisallowedPackage() {
+ NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ NotificationListenerFilter nlf2 =
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
+ mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
+
+ String[] pkgs = new String[] {"pkg1"};
+ int[] uids = new int[] {243};
+ mListeners.onPackagesChanged(true, pkgs, uids);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
+ .getDisallowedPackages()).isEmpty();
+ }
+
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e8888f4..a640509 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -464,7 +464,7 @@
// Setup managed services
when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
- when(mNlf.isPackageAllowed(anyString())).thenReturn(true);
+ when(mNlf.isPackageAllowed(any())).thenReturn(true);
when(mNlf.isPackageAllowed(null)).thenReturn(true);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
@@ -7307,7 +7307,7 @@
@Test
public void testIsVisibleToListener_disallowedPackage() {
- when(mNlf.isPackageAllowed(null)).thenReturn(false);
+ when(mNlf.isPackageAllowed(any())).thenReturn(false);
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getUserId()).thenReturn(10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 72b8439..aa1110c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -719,7 +719,7 @@
final ActivityRecord activity = createActivityWithTask();
assertTrue(activity.hasSavedState());
- ActivityRecord.activityResumedLocked(activity.appToken);
+ ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */);
assertFalse(activity.hasSavedState());
assertNull(activity.getSavedState());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 5597be9..2686a24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -27,6 +27,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -118,6 +119,19 @@
}
@Test
+ public void testRegisterOrganizer_ignoreUntrustedDisplay() throws RemoteException {
+ doReturn(false).when(mDisplayContent).isTrusted();
+
+ final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+ List<DisplayAreaAppearedInfo> infos = mOrganizerController
+ .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList();
+
+ assertThat(infos).isEmpty();
+ verify(organizer, never()).onDisplayAreaAppeared(any(DisplayAreaInfo.class),
+ any(SurfaceControl.class));
+ }
+
+ @Test
public void testCreateTaskDisplayArea_topBelowRoot() {
final String newTdaName = "testTda";
final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
@@ -186,13 +200,20 @@
@Test
public void testCreateTaskDisplayArea_invalidDisplayAndRoot() {
final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+
assertThrows(IllegalArgumentException.class, () ->
mOrganizerController.createTaskDisplayArea(
organizer, SystemServicesTestRule.sNextDisplayId + 1, FEATURE_ROOT,
"testTda"));
+
assertThrows(IllegalArgumentException.class, () ->
mOrganizerController.createTaskDisplayArea(
organizer, DEFAULT_DISPLAY, FEATURE_ROOT - 1, "testTda"));
+
+ doReturn(false).when(mDisplayContent).isTrusted();
+ assertThrows(IllegalArgumentException.class, () ->
+ mOrganizerController.createTaskDisplayArea(
+ organizer, DEFAULT_DISPLAY, FEATURE_ROOT, "testTda"));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 89b962b..d4c956d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -43,11 +43,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -57,6 +61,7 @@
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.window.IDisplayAreaOrganizer;
import com.google.android.collect.Lists;
@@ -525,6 +530,42 @@
assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isEqualTo(tda);
}
+ @Test
+ public void testDisplayContentUpdateDisplayAreaOrganizers_onDisplayAreaAppeared() {
+ final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+ mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+ final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+ spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+ when(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+ .getOrganizerByFeature(FEATURE_VENDOR_FIRST))
+ .thenReturn(mockDisplayAreaOrganizer);
+
+ mDisplayContent.addChild(displayArea, 0);
+ mDisplayContent.updateDisplayAreaOrganizers();
+
+ assertEquals(mockDisplayAreaOrganizer, displayArea.mOrganizer);
+ verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+ .onDisplayAreaAppeared(
+ eq(mockDisplayAreaOrganizer),
+ argThat(it -> it == displayArea && it.getSurfaceControl() != null));
+ }
+
+ @Test
+ public void testRemoveImmediately_onDisplayAreaVanished() {
+ final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+ mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+ final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+ displayArea.mOrganizer = mockDisplayAreaOrganizer;
+ spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+ mDisplayContent.addChild(displayArea, 0);
+
+ displayArea.removeImmediately();
+
+ assertNull(displayArea.mOrganizer);
+ verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+ .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea);
+ }
+
private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
private TestDisplayArea(WindowManagerService wms, Rect bounds) {
super(wms, ANY, "half display area");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index b4fd302..781cfec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1834,7 +1834,7 @@
mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
}
- private void performLayout(DisplayContent dc) {
+ static void performLayout(DisplayContent dc) {
dc.setLayoutNeeded();
dc.performLayout(true /* initial */, false /* updateImeWindows */);
}
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/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 77fca3d..9c16143 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -549,6 +549,9 @@
public void removeStartingWindow(int taskId) { }
@Override
+ public void copySplashScreenView(int taskId) { }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@Override
@@ -609,10 +612,10 @@
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
}
-
@Override
public void removeStartingWindow(int taskId) { }
-
+ @Override
+ public void copySplashScreenView(int taskId) { }
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -685,7 +688,8 @@
@Override
public void removeStartingWindow(int taskId) { }
-
+ @Override
+ public void copySplashScreenView(int taskId) { }
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -829,6 +833,8 @@
@Override
public void removeStartingWindow(int taskId) { }
@Override
+ public void copySplashScreenView(int taskId) { }
+ @Override
public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) {
mInfo = info;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3231f8b..8969695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -67,11 +67,14 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.when;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
import android.view.InputWindowHandle;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -559,6 +562,46 @@
assertTrue(window.isVisibleByPolicy());
}
+ @Test
+ public void testCompatOverrideScale() {
+ final float overrideScale = 2; // 0.5x on client side.
+ final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
+ spyOn(cmp);
+ doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
+ final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
+ makeWindowVisible(w);
+ w.setRequestedSize(100, 200);
+ w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+ DisplayContentTests.performLayout(mDisplayContent);
+
+ // Frame on screen = 100x200. Compat frame on client = 50x100.
+ final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
+ unscaledCompatFrame.scale(overrideScale);
+ assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+
+ // Surface should apply the scale.
+ w.prepareSurfaces();
+ verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
+ overrideScale, 0, 0, overrideScale);
+
+ // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
+ final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
+ mContext.getApplicationInfo());
+ final Configuration winConfig = w.getConfiguration();
+ final Configuration clientConfig = new Configuration(w.getConfiguration());
+ compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
+
+ assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
+ assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);
+ assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp);
+ assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale));
+
+ final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds());
+ unscaledClientBounds.scale(overrideScale);
+ assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
+ }
+
@UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eb6c6ed..c13d6b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -337,6 +337,7 @@
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
attrs.setTitle(name);
+ attrs.packageName = "test";
final WindowState w = new WindowState(service, session, iWindow, token, parent,
OP_NONE, attrs, VISIBLE, ownerId, userId,
@@ -1143,6 +1144,9 @@
public void removeStartingWindow(int taskId) {
}
@Override
+ public void copySplashScreenView(int taskId) {
+ }
+ @Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
}
@Override
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/OWNERS b/services/usb/OWNERS
index 8ee72b5..60172a3 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,6 +1,5 @@
badhri@google.com
elaurent@google.com
-moltmann@google.com
albertccwang@google.com
jameswei@google.com
howardyen@google.com
\ No newline at end of file
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/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
- PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+ PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
+ "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
+ " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 99f2e5e..c6757fb 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -646,7 +646,7 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED})
public @interface OverrideNetworkType {}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 21cf3e5..3f6162d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5351,11 +5351,28 @@
public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED =
KEY_PREFIX + "suggestion_ssid_list_with_mac_randomization_disabled";
+ /**
+ * Avoid SoftAp in 5GHz if cellular is on unlicensed 5Ghz using License Assisted Access
+ * (LAA).
+ */
+ public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL =
+ KEY_PREFIX + "avoid_5ghz_softap_for_laa_bool";
+
+ /**
+ * Avoid Wifi Direct in 5GHz if cellular is on unlicensed 5Ghz using License Assisted
+ * Access (LAA).
+ */
+ public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL =
+ KEY_PREFIX + "avoid_5ghz_wifi_direct_for_laa_bool";
+
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putInt(KEY_HOTSPOT_MAX_CLIENT_COUNT, 0);
defaults.putStringArray(KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED,
new String[0]);
+ defaults.putBoolean(KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, false);
+ defaults.putBoolean(KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, false);
return defaults;
}
diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java
deleted file mode 100644
index 7c7eb9f..0000000
--- a/telephony/java/android/telephony/RadioInterfaceCapabilities.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.util.ArraySet;
-
-/**
- * Contains the set of supported capabilities that the Radio Interface supports on this device.
- *
- * @hide
- */
-public class RadioInterfaceCapabilities {
-
- private final ArraySet<String> mSupportedCapabilities;
-
-
- public RadioInterfaceCapabilities() {
- mSupportedCapabilities = new ArraySet<>();
- }
-
- /**
- * Marks a capability as supported
- *
- * @param capabilityName the name of the capability
- */
- public void addSupportedCapability(
- @TelephonyManager.RadioInterfaceCapability String capabilityName) {
- mSupportedCapabilities.add(capabilityName);
- }
-
- /**
- * Whether the capability is supported
- *
- * @param capabilityName the name of the capability
- */
- public boolean isSupported(String capabilityName) {
- return mSupportedCapabilities.contains(capabilityName);
- }
-}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 1fcb504..5b5570b 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -66,9 +66,26 @@
* {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
* capability or is currently connected to the secondary
* {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+ * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead.
*/
+ @Deprecated
public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+ /**
+ * Override network type when the device is connected NR cellular network and the data rate is
+ * higher than the generic 5G date rate.
+ * Including but not limited to
+ * <ul>
+ * <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
+ * <li>The device is connected to the specific network which the carrier is using
+ * proprietary means to provide a faster overall data connection than would be otherwise
+ * possible. This may include using other bands unique to the carrier, or carrier
+ * aggregation, for example.</li>
+ * </ul>
+ * One of the use case is that UX can show a different icon, for example, "5G+"
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+
@NetworkType
private final int mNetworkType;
@@ -169,7 +186,7 @@
case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
- case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
default: return "UNKNOWN";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 16ffd9e..ee3a0ef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8491,6 +8491,11 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param subId the id of the subscription to set the preferred network type for.
* @param networkType the preferred network type
@@ -8524,6 +8529,11 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param networkTypeBitmask The bitmask of preferred network types.
* @return true on success; false on any failure.
@@ -8550,6 +8560,11 @@
* Set the allowed network types of the device. This is for carrier or privileged apps to
* enable/disable certain network types on the device. The user preferred network types should
* be set through {@link #setPreferredNetworkTypeBitmask}.
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param allowedNetworkTypes The bitmask of allowed network types.
* @return true on success; false on any failure.
@@ -8624,6 +8639,11 @@
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
* including the configuration done through other reasons.
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param reason the reason the allowed network type change is taking place
* @param allowedNetworkTypes The bitmask of allowed network types.
@@ -14861,10 +14881,24 @@
public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE =
"CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+ /**
+ * Indicates whether {@link #setPreferredNetworkType}, {@link
+ * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and
+ * {@link #setAllowedNetworkTypesForReason} rely on
+ * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio
+ * interface.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
+ "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
+ CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
})
public @interface RadioInterfaceCapability {}
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 401d87a..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -125,21 +125,11 @@
}
/**
- * Test rollbacks of staged installs involving only apks with bad update.
- * Trigger rollback phase.
- */
- @Test
- public void testBadApkOnly_Phase3_Crash() throws Exception {
- // One more crash to trigger rollback
- RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
- }
-
- /**
* Test rollbacks of staged installs involving only apks.
* Confirm rollback phase.
*/
@Test
- public void testBadApkOnly_Phase4_VerifyRollback() throws Exception {
+ public void testBadApkOnly_Phase3_VerifyRollback() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -447,8 +437,10 @@
Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
Rollback.from(TestApp.A, 0).to(TestApp.A1));
- // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
- RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT-1 times
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+ // Sleep for a while to make sure we don't trigger rollback
+ Thread.sleep(TimeUnit.SECONDS.toMillis(30));
}
@Test
@@ -504,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 1d5730f..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
@@ -153,13 +153,14 @@
getDevice().reboot();
runPhase("testBadApkOnly_Phase2_VerifyInstall");
- // Trigger rollback and wait for reboot to happen
- runPhase("testBadApkOnly_Phase3_Crash");
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
getDevice().waitForDeviceAvailable();
- runPhase("testBadApkOnly_Phase4_VerifyRollback");
+ runPhase("testBadApkOnly_Phase3_VerifyRollback");
assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
@@ -304,8 +305,10 @@
getDevice().reboot();
// Verify apex was installed and then crash the apk
runPhase("testRollbackApexWithApkCrashing_Phase2_Crash");
- // Wait for crash to trigger rollback
- waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
+ waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
getDevice().waitForDeviceAvailable();
// Verify rollback occurred due to crash of apk-in-apex
runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback");
@@ -551,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";
@@ -631,6 +658,12 @@
}
}
+ private void startActivity(String packageName) throws Exception {
+ String cmd = "am start -S -a android.intent.action.MAIN "
+ + "-c android.intent.category.LAUNCHER " + packageName;
+ getDevice().executeShellCommand(cmd);
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
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/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
index 6bed5a8..4c60ccf 100644
--- a/tests/net/AndroidManifest.xml
+++ b/tests/net/AndroidManifest.xml
@@ -48,6 +48,7 @@
<uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.NETWORK_FACTORY" />
<uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
+ <uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index d232a507..fd29a95 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -40,7 +40,7 @@
@SmallTest
public class OemNetworkPreferencesTest {
- private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT;
+ private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
private static final String TEST_PACKAGE = "com.google.apps.contacts";
private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder();
@@ -54,7 +54,7 @@
@Test
public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() {
assertThrows(NullPointerException.class,
- () -> mBuilder.removeNetworkPreference(null));
+ () -> mBuilder.clearNetworkPreference(null));
}
@Test
@@ -129,7 +129,7 @@
assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
- mBuilder.removeNetworkPreference(TEST_PACKAGE);
+ mBuilder.clearNetworkPreference(TEST_PACKAGE);
networkPreferences = mBuilder.build().getNetworkPreferences();
assertFalse(networkPreferences.containsKey(TEST_PACKAGE));
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/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java
index 95a7942..c548e30 100644
--- a/tests/net/java/android/net/VpnManagerTest.java
+++ b/tests/net/java/android/net/VpnManagerTest.java
@@ -49,7 +49,7 @@
private static final String IDENTITY_STRING = "Identity";
private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
- private IConnectivityManager mMockCs;
+ private IVpnManager mMockService;
private VpnManager mVpnManager;
private final MockContext mMockContext =
new MockContext() {
@@ -61,24 +61,26 @@
@Before
public void setUp() throws Exception {
- mMockCs = mock(IConnectivityManager.class);
- mVpnManager = new VpnManager(mMockContext, mMockCs);
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
}
@Test
public void testProvisionVpnProfilePreconsented() throws Exception {
final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true);
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
// Expect there to be no intent returned, as consent has already been granted.
assertNull(mVpnManager.provisionVpnProfile(profile));
- verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
}
@Test
public void testProvisionVpnProfileNeedsConsent() throws Exception {
final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false);
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
// Expect intent to be returned, as consent has not already been granted.
final Intent intent = mVpnManager.provisionVpnProfile(profile);
@@ -88,25 +90,25 @@
ComponentName.unflattenFromString(
"com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
assertEquals(expectedComponentName, intent.getComponent());
- verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
}
@Test
public void testDeleteProvisionedVpnProfile() throws Exception {
mVpnManager.deleteProvisionedVpnProfile();
- verify(mMockCs).deleteVpnProfile(eq(PKG_NAME));
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
}
@Test
public void testStartProvisionedVpnProfile() throws Exception {
mVpnManager.startProvisionedVpnProfile();
- verify(mMockCs).startVpnProfile(eq(PKG_NAME));
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
}
@Test
public void testStopProvisionedVpnProfile() throws Exception {
mVpnManager.stopProvisionedVpnProfile();
- verify(mMockCs).stopVpnProfile(eq(PKG_NAME));
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
}
private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
index 2fd5e38..866f38c 100644
--- a/tests/net/java/android/net/VpnTransportInfoTest.java
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -17,7 +17,6 @@
package android.net;
import static com.android.testutils.ParcelUtils.assertParcelSane;
-import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -36,7 +35,6 @@
public void testParceling() {
VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
assertParcelSane(v, 1 /* fieldCount */);
- assertParcelingIsLossless(v);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e639a36..2a693eb 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -200,6 +200,7 @@
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
+import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
@@ -376,6 +377,7 @@
private MockContext mServiceContext;
private HandlerThread mCsHandlerThread;
+ private HandlerThread mVMSHandlerThread;
private ConnectivityService.Dependencies mDeps;
private ConnectivityService mService;
private WrappedConnectivityManager mCm;
@@ -390,6 +392,7 @@
private TestNetIdManager mNetIdManager;
private QosCallbackMockHelper mQosCallbackMockHelper;
private QosCallbackTracker mQosCallbackTracker;
+ private VpnManagerService mVpnManagerService;
// State variables required to emulate NetworkPolicyManagerService behaviour.
private int mUidRules = RULE_NONE;
@@ -1262,24 +1265,55 @@
r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
}
- private void mockVpn(int uid) {
- synchronized (mService.mVpns) {
- int userId = UserHandle.getUserId(uid);
- mMockVpn = new MockVpn(userId);
- // This has no effect unless the VPN is actually connected, because things like
- // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
- // netId, and check if that network is actually connected.
- mService.mVpns.put(userId, mMockVpn);
- }
+ private VpnManagerService makeVpnManagerService() {
+ final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
+ public int getCallingUid() {
+ return mDeps.getCallingUid();
+ }
+
+ public HandlerThread makeHandlerThread() {
+ return mVMSHandlerThread;
+ }
+
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ public INetd getNetd() {
+ return mMockNetd;
+ }
+
+ public INetworkManagementService getINetworkManagementService() {
+ return mNetworkManagementService;
+ }
+ };
+ return new VpnManagerService(mServiceContext, deps);
+ }
+
+ private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
+ assertNotNull(nc);
+ final TransportInfo ti = nc.getTransportInfo();
+ assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti,
+ ti instanceof VpnTransportInfo);
+ assertEquals(type, ((VpnTransportInfo) ti).type);
+
}
private void processBroadcastForVpn(Intent intent) {
- // The BroadcastReceiver for this broadcast checks it is being run on the handler thread.
- final Handler handler = new Handler(mCsHandlerThread.getLooper());
- handler.post(() -> mServiceContext.sendBroadcast(intent));
+ mServiceContext.sendBroadcast(intent);
+ HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
waitForIdle();
}
+ private void mockVpn(int uid) {
+ synchronized (mVpnManagerService.mVpns) {
+ int userId = UserHandle.getUserId(uid);
+ mMockVpn = new MockVpn(userId);
+ // Every running user always has a Vpn in the mVpns array, even if no VPN is running.
+ mVpnManagerService.mVpns.put(userId, mMockVpn);
+ }
+ }
+
private void mockUidNetworkingBlocked() {
doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class)
.checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules,
@@ -1394,6 +1428,7 @@
FakeSettingsProvider.clearSettingsProvider();
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
new FakeSettingsProvider());
+ mServiceContext.setUseRegisteredHandlers(true);
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(
NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
@@ -1403,6 +1438,7 @@
initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
mCsHandlerThread = new HandlerThread("TestConnectivityService");
+ mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
mDeps = makeDependencies();
returnRealCallingUid();
mService = new ConnectivityService(mServiceContext,
@@ -1425,6 +1461,8 @@
// getSystemService() correctly.
mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
mService.systemReadyInternal();
+ mVpnManagerService = makeVpnManagerService();
+ mVpnManagerService.systemReady();
mockVpn(Process.myUid());
mCm.bindProcessToNetwork(null);
mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -1452,7 +1490,6 @@
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
- doReturn(mKeyStore).when(deps).getKeyStore();
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -2717,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(),
@@ -3873,6 +3906,24 @@
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
+ @Test
+ public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(false /* validated */);
+
+ final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ assertThrows(SecurityException.class,
+ () -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
+ callback.assertNoCallback();
+
+ mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+ PERMISSION_GRANTED);
+ mCm.registerSystemDefaultNetworkCallback(callback, handler);
+ callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ mCm.unregisterNetworkCallback(callback);
+ }
+
private void setCaptivePortalMode(int mode) {
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
@@ -4062,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);
@@ -5966,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)
@@ -5975,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 */);
@@ -6484,6 +6532,8 @@
assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+
+ assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
}
private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
@@ -6523,6 +6573,7 @@
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
// A VPN without underlying networks is not suspended.
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
final int userId = UserHandle.getUserId(Process.myUid());
assertDefaultNetworkCapabilities(userId /* no networks */);
@@ -6686,6 +6737,7 @@
// By default, VPN is set to track default network (i.e. its underlying networks is null).
// In case of no default network, VPN is considered metered.
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
// Connect to Cell; Cell is the default network.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -6743,6 +6795,7 @@
NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertNotNull("nc=" + nc, nc.getUids());
assertEquals(nc.getUids(), uidRangesForUid(uid));
+ assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
// Set an underlying network and expect to see the VPN transports change.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6825,8 +6878,8 @@
// Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
final ArrayList<String> allowList = new ArrayList<>();
- mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */,
- allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+ true /* lockdown */, allowList);
waitForIdle();
assertNull(mCm.getActiveNetworkForUid(uid));
// This is arguably overspecified: a UID that is not running doesn't have an active network.
@@ -6856,7 +6909,8 @@
assertNull(mCm.getActiveNetworkForUid(uid));
assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
- mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+ allowList);
waitForIdle();
}
@@ -7232,7 +7286,8 @@
final int uid = Process.myUid();
final int userId = UserHandle.getUserId(uid);
final ArrayList<String> allowList = new ArrayList<>();
- mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+ allowList);
waitForIdle();
UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
@@ -7254,7 +7309,7 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown, expect to see the network unblocked.
- mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
vpnUidCallback.assertNoCallback();
@@ -7267,7 +7322,8 @@
// Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
allowList.add(TEST_PACKAGE_NAME);
- mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+ allowList);
callback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
@@ -7300,11 +7356,12 @@
// Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
// Everything should now be blocked.
- mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
waitForIdle();
expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
allowList.clear();
- mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+ allowList);
waitForIdle();
expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
@@ -7317,7 +7374,7 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown. Everything is unblocked.
- mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
@@ -7329,7 +7386,8 @@
// Enable and disable an always-on VPN package without lockdown. Expect no changes.
reset(mMockNetd);
- mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */,
+ allowList);
inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
callback.assertNoCallback();
defaultCallback.assertNoCallback();
@@ -7340,7 +7398,7 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
callback.assertNoCallback();
defaultCallback.assertNoCallback();
@@ -7352,7 +7410,8 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// Enable lockdown and connect a VPN. The VPN is not blocked.
- mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+ allowList);
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
@@ -7398,11 +7457,14 @@
when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
}
- private void establishLegacyLockdownVpn() throws Exception {
+ private void establishLegacyLockdownVpn(Network underlying) throws Exception {
+ // The legacy lockdown VPN only supports userId 0, and must have an underlying network.
+ assertNotNull(underlying);
mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
// The legacy lockdown VPN only supports userId 0.
final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
mMockVpn.registerAgent(ranges);
+ mMockVpn.setUnderlyingNetworks(new Network[]{underlying});
mMockVpn.connect(true);
}
@@ -7410,6 +7472,9 @@
public void testLegacyLockdownVpn() throws Exception {
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+ // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7418,6 +7483,10 @@
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultCallback);
+ final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+ mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+ new Handler(ConnectivityThread.getInstanceLooper()));
+
// Pretend lockdown VPN was configured.
setupLegacyLockdownVpn();
@@ -7447,6 +7516,7 @@
mCellNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+ systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
waitForIdle();
assertNull(mMockVpn.getAgent());
@@ -7458,6 +7528,8 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
+ mCellNetworkAgent);
waitForIdle();
assertNull(mMockVpn.getAgent());
@@ -7467,6 +7539,7 @@
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
b1.expectBroadcast();
// When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
@@ -7476,6 +7549,7 @@
mCellNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+ systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
b1.expectBroadcast();
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7498,9 +7572,10 @@
mMockVpn.expectStartLegacyVpnRunner();
b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
- establishLegacyLockdownVpn();
+ establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
callback.expectAvailableThenValidatedCallbacks(mMockVpn);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ systemDefaultCallback.assertNoCallback();
NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
b1.expectBroadcast();
b2.expectBroadcast();
@@ -7512,9 +7587,7 @@
assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
- VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo();
- assertNotNull(ti);
- assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type);
+ assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY);
// Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
final LinkProperties wifiLp = new LinkProperties();
@@ -7542,11 +7615,10 @@
// fact that a VPN is connected should only result in the VPN itself being unblocked, not
// any other network. Bug in isUidBlockedByVpn?
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
callback.expectCallback(CallbackEntry.LOST, mMockVpn);
- defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+ systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// While the VPN is reconnecting on the new network, everything is blocked.
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7557,9 +7629,10 @@
// The VPN comes up again on wifi.
b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
- establishLegacyLockdownVpn();
+ establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
callback.expectAvailableThenValidatedCallbacks(mMockVpn);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ systemDefaultCallback.assertNoCallback();
b1.expectBroadcast();
b2.expectBroadcast();
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7573,14 +7646,10 @@
assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect cell. Nothing much happens since it's not the default network.
- // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
- // NetworkInfo is updated. This is probably a bug.
- // TODO: consider fixing this.
- b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
mCellNetworkAgent.disconnect();
- b1.expectBroadcast();
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultCallback.assertNoCallback();
+ systemDefaultCallback.assertNoCallback();
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -7590,6 +7659,7 @@
b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
b1.expectBroadcast();
callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
@@ -7641,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/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 799bcc8..c86224a 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -33,6 +33,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.IpSecAlgorithm;
@@ -44,6 +45,7 @@
import android.net.IpSecTunnelInterfaceResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.Network;
import android.os.Binder;
import android.os.INetworkManagementService;
@@ -53,6 +55,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.IpSecService.TunnelInterfaceRecord;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -109,6 +113,7 @@
};
AppOpsManager mMockAppOps = mock(AppOpsManager.class);
+ ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
MockContext mMockContext = new MockContext() {
@Override
@@ -116,12 +121,22 @@
switch(name) {
case Context.APP_OPS_SERVICE:
return mMockAppOps;
+ case Context.CONNECTIVITY_SERVICE:
+ return mMockConnectivityMgr;
default:
return null;
}
}
@Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (ConnectivityManager.class == serviceClass) {
+ return Context.CONNECTIVITY_SERVICE;
+ }
+ return null;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mMockPkgMgr;
}
@@ -151,6 +166,10 @@
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
private static final int REMOTE_ENCAP_PORT = 4500;
+ private static final String BLESSED_PACKAGE = "blessedPackage";
+ private static final String SYSTEM_PACKAGE = "systemPackage";
+ private static final String BAD_PACKAGE = "badPackage";
+
public IpSecServiceParameterizedTest(
String sourceAddr, String destAddr, String localInnerAddr, int family) {
mSourceAddr = sourceAddr;
@@ -174,15 +193,15 @@
when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
// A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
- .thenReturn(AppOpsManager.MODE_ALLOWED);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
// A system package will not be granted the app op, so this should fall back to
// a permissions check, which should pass.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage")))
- .thenReturn(AppOpsManager.MODE_DEFAULT);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
// A mismatch between the package name and the UID will return MODE_IGNORED.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage")))
- .thenReturn(AppOpsManager.MODE_IGNORED);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
}
//TODO: Add a test to verify SPI.
@@ -338,7 +357,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -352,7 +371,7 @@
ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -370,14 +389,14 @@
if (mFamily == AF_INET) {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
} else {
try {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
} catch (IllegalArgumentException expected) {
}
@@ -396,14 +415,14 @@
if (mFamily == AF_INET) {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
} else {
try {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
} catch (IllegalArgumentException expected) {
}
@@ -417,12 +436,12 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
// Attempting to create transform a second time with the same SPIs should throw an error...
try {
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("IpSecService should have thrown an error for reuse of SPI");
} catch (IllegalStateException expected) {
}
@@ -430,7 +449,7 @@
// ... even if the transform is deleted
mIpSecService.deleteTransform(createTransformResp.resourceId);
try {
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("IpSecService should have thrown an error for reuse of SPI");
} catch (IllegalStateException expected) {
}
@@ -443,7 +462,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -467,7 +486,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
mIpSecService.deleteTransform(createTransformResp.resourceId);
verify(mMockNetd, times(1))
@@ -515,7 +534,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
@@ -562,7 +581,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
if (closeSpiBeforeApply) {
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -592,7 +611,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
// Close SPI record
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -638,7 +657,7 @@
@Test
public void testCreateTunnelInterface() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
// Check that we have stored the tracking object, and retrieve it
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
@@ -661,11 +680,11 @@
@Test
public void testDeleteTunnelInterface() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
- mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
+ mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
@@ -678,10 +697,73 @@
}
}
+ private Network createFakeUnderlyingNetwork(String interfaceName) {
+ final Network fakeNetwork = new Network(1000);
+ final LinkProperties fakeLp = new LinkProperties();
+ fakeLp.setInterfaceName(interfaceName);
+ when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
+ return fakeNetwork;
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterface() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+
+ final IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+ assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
+ fail("Expected an IllegalArgumentException for invalid resource ID.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+ final IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+
+ final Network newFakeNetwork =
+ createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException because the underlying network is the"
+ + " network being exposed by this tunnel.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
@Test
public void testTunnelInterfaceBinderDeath() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
@@ -718,9 +800,9 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
if (closeSpiBeforeApply) {
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -728,8 +810,8 @@
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
- mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
- transformResourceId, "blessedPackage");
+ mIpSecService.applyTunnelModeTransform(
+ tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
for (int selAddrFamily : ADDRESS_FAMILIES) {
verify(mMockNetd)
@@ -758,17 +840,17 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
// Close SPI record
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
- mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
- transformResourceId, "blessedPackage");
+ mIpSecService.applyTunnelModeTransform(
+ tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
for (int selAddrFamily : ADDRESS_FAMILIES) {
verify(mMockNetd)
@@ -790,7 +872,7 @@
@Test
public void testAddRemoveAddressFromTunnelInterface() throws Exception {
- for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
+ for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
mIpSecService.addAddressToTunnelInterface(
@@ -816,7 +898,7 @@
public void testAddTunnelFailsForBadPackageName() throws Exception {
try {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
fail("Expected a SecurityException for badPackage.");
} catch (SecurityException expected) {
}
@@ -830,7 +912,7 @@
try {
String addr = Inet4Address.getLoopbackAddress().getHostAddress();
mIpSecService.createTunnelInterface(
- addr, addr, new Network(0), new Binder(), "blessedPackage");
+ addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
fail("Expected UnsupportedOperationException for disabled feature");
} catch (UnsupportedOperationException expected) {
}
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
similarity index 64%
rename from tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
rename to tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index d1d6a26..0f920b3 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -16,8 +16,11 @@
package com.android.framework.permission.tests;
+import android.content.Context;
import android.os.Binder;
-import android.os.IVibratorService;
+import android.os.CombinedVibrationEffect;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -32,27 +35,28 @@
* Verify that Hardware apis cannot be called without required permissions.
*/
@SmallTest
-public class VibratorServicePermissionTest extends TestCase {
+public class VibratorManagerServicePermissionTest extends TestCase {
- private IVibratorService mVibratorService;
+ private IVibratorManagerService mVibratorService;
@Override
protected void setUp() throws Exception {
- mVibratorService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mVibratorService = IVibratorManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
}
/**
- * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions.
+ * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String,
+ * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions.
* <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
+ * {@link android.Manifest.permission#VIBRATE}
*/
public void testVibrate() throws RemoteException {
try {
- final VibrationEffect effect =
- VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
- final VibrationAttributes attrs = new VibrationAttributes.Builder()
+ CombinedVibrationEffect effect =
+ CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+ VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_ALARM)
.build();
mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
@@ -64,10 +68,10 @@
}
/**
- * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
+ * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires
+ * permissions.
* <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
+ * {@link android.Manifest.permission#VIBRATE}
*/
public void testCancelVibrate() throws RemoteException {
try {
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 1102624..4a1f96d 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -42,6 +42,8 @@
private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>();
+ private boolean mUseRegisteredHandlers;
+
public abstract class FutureIntent extends FutureTask<Intent> {
public FutureIntent() {
super(
@@ -61,17 +63,24 @@
public class BroadcastInterceptor extends FutureIntent {
private final BroadcastReceiver mReceiver;
private final IntentFilter mFilter;
+ private final Handler mHandler;
- public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+ public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter,
+ Handler handler) {
mReceiver = receiver;
mFilter = filter;
+ mHandler = mUseRegisteredHandlers ? handler : null;
}
public boolean dispatchBroadcast(Intent intent) {
if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
if (mReceiver != null) {
final Context context = BroadcastInterceptingContext.this;
- mReceiver.onReceive(context, intent);
+ if (mHandler == null) {
+ mReceiver.onReceive(context, intent);
+ } else {
+ mHandler.post(() -> mReceiver.onReceive(context, intent));
+ }
return false;
} else {
set(intent);
@@ -116,25 +125,38 @@
}
public FutureIntent nextBroadcastIntent(IntentFilter filter) {
- final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+ final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null);
synchronized (mInterceptors) {
mInterceptors.add(interceptor);
}
return interceptor;
}
+ /**
+ * Whether to send broadcasts to registered handlers. By default, receivers are called
+ * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is
+ * instead called by a runnable posted to the Handler specified when the receiver was
+ * registered. This method applies only to future registrations, already-registered receivers
+ * are unaffected.
+ */
+ public void setUseRegisteredHandlers(boolean use) {
+ synchronized (mInterceptors) {
+ mUseRegisteredHandlers = use;
+ }
+ }
+
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- synchronized (mInterceptors) {
- mInterceptors.add(new BroadcastInterceptor(receiver, filter));
- }
- return null;
+ return registerReceiver(receiver, filter, null, null);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
- return registerReceiver(receiver, filter);
+ synchronized (mInterceptors) {
+ mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
+ }
+ return null;
}
@Override
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..a6eae96 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
@@ -121,6 +129,9 @@
@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 +173,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 8643d8a..49ce54d 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -16,12 +16,18 @@
package com.android.server.vcn;
+import static android.net.IpSecManager.IpSecTunnelInterface;
+
+import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import android.net.IpSecManager;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,7 +43,13 @@
public void setUp() throws Exception {
super.setUp();
- mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
+ final IpSecTunnelInterface tunnelIface =
+ mContext.getSystemService(IpSecManager.class)
+ .createIpSecTunnelInterface(
+ DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
+ mGatewayConnection.setTunnelInterface(tunnelIface);
+
+ // Don't need to transition to DisconnectedState because it is the starting state
mTestLooper.dispatchAll();
}
@@ -67,6 +79,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -77,6 +90,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -86,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());
+ }
}