diff options
576 files changed, 19529 insertions, 5521 deletions
diff --git a/Android.bp b/Android.bp index 0a3ca3b808ea..240d803ef337 100644 --- a/Android.bp +++ b/Android.bp @@ -403,6 +403,7 @@ filegroup { ":framework-mediaprovider-sources", ":framework-permission-sources", ":framework-permission-s-sources", + ":framework-scheduling-sources", ":framework-sdkextensions-sources", ":framework-statsd-sources", ":framework-tethering-srcs", @@ -423,6 +424,7 @@ java_library { "framework-mediaprovider.stubs.module_lib", "framework-permission.stubs.module_lib", "framework-permission-s.stubs.module_lib", + "framework-scheduling.stubs.module_lib", "framework-sdkextensions.stubs.module_lib", "framework-statsd.stubs.module_lib", "framework-tethering.stubs.module_lib", @@ -443,6 +445,7 @@ java_library { "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 3f2e89889912..4bd524f229ca 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -315,6 +315,7 @@ java_library_static { "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 @@ java_library_static { "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 @@ java_library_static { "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 97cfe36fca80..cd75b1456ba8 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 @@ import java.util.Map; 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 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * 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 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** + * 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 final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl 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 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl mFailures.put(key, result); mSuccesses.remove(key); } + mAll.put(key, result); return this; } @@ -189,7 +215,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl 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 76225e40c56e..440f63341151 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.annotation.Nullable; 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 @@ import java.util.Objects; */ 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 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * 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 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * 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 @@ public final class AppSearchResult<ValueType> implements Parcelable { * * <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 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new successful {@link AppSearchResult}. + * * @hide */ @NonNull @@ -215,6 +219,7 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new failed {@link AppSearchResult}. + * * @hide */ @NonNull @@ -227,6 +232,8 @@ public final class AppSearchResult<ValueType> implements Parcelable { @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 @@ public final class AppSearchResult<ValueType> implements Parcelable { } 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 8fcd2f9bffb2..73ca0ccab46d 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -108,54 +108,85 @@ public final class AppSearchSession implements Closeable { * 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. + * <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. + * <p>Supplying a schema with such changes will, by default, result in this call completing its + * future with an {@link android.app.appsearch.exceptions.AppSearchException} with a code of + * {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility. + * In this case the previously set schema will remain active. + * + * <p>If you need to make non-backwards-compatible changes as described above, you can either: + * + * <ul> + * <li>Set the {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In + * this case, instead of completing its future with an {@link + * android.app.appsearch.exceptions.AppSearchException} with the {@link + * AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not + * compatible with the new schema will be deleted and the incompatible schema will be + * applied. Incompatible types and deleted types will be set into {@link + * SetSchemaResponse#getIncompatibleTypes()} and {@link + * SetSchemaResponse#getDeletedTypes()}, respectively. + * <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type + * and make no deletion. The migrator will migrate documents from it's old schema version + * to the new version. Migrated types will be set into both {@link + * SetSchemaResponse#getIncompatibleTypes()} and {@link + * SetSchemaResponse#getMigratedTypes()}. See the migration section below. + * </ul> * * <p>It is a no-op to set the same schema as has been previously set; this is handled * efficiently. * - * <p>By default, documents are visible on platform surfaces. To opt out, call - * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as - * false. Any visibility settings apply only to the schemas that are included in the - * {@code request}. Visibility settings for a schema type do not persist across - * {@link #setSchema} calls. + * <p>By default, documents are visible on platform surfaces. To opt out, call {@code + * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any + * visibility settings apply only to the schemas that are included in the {@code request}. + * Visibility settings for a schema type do not apply or persist across {@link + * SetSchemaRequest}s. + * + * <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 request The schema update request. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive errors resulting from setting the schema. If the * operation succeeds, the callback will be invoked with {@code null}. + * @see android.app.appsearch.AppSearchSchema.Migrator + * @see android.app.appsearch.AppSearchMigrationHelper.Transformer */ public void setSchema( @NonNull SetSchemaRequest request, @@ -193,11 +224,9 @@ public final class AppSearchSession implements Closeable { executor.execute(() -> { if (result.isSuccess()) { callback.accept( - // TODO(b/151178558) implement Migration in platform. + // TODO(b/177266929) implement Migration in platform. AppSearchResult.newSuccessfulResult( - new SetSchemaResponse.Builder().setResultCode( - result.getResultCode()) - .build())); + new SetSchemaResponse.Builder().build())); } else { callback.accept(result); } @@ -256,7 +285,7 @@ public final class AppSearchSession implements Closeable { * <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a * schema type previously registered via the {@link #setSchema} method. * - * @param request {@link PutDocumentsRequest} containing documents to be indexed + * @param request {@link PutDocumentsRequest} containing documents to be indexed * @param executor Executor on which to invoke the callback. * @param callback Callback to receive pending result of performing this operation. The keys * of the returned {@link AppSearchBatchResult} are the URIs of the input @@ -297,9 +326,10 @@ public final class AppSearchSession implements Closeable { } /** - * Retrieves {@link GenericDocument}s by URI. + * Gets {@link GenericDocument} objects by URIs and namespace from the {@link AppSearchSession} + * database. * - * @param request {@link GetByUriRequest} containing URIs to be retrieved. + * @param request a request containing URIs and namespace to get documents for. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. The keys * of the returned {@link AppSearchBatchResult} are the input URIs. The values @@ -377,48 +407,65 @@ public final class AppSearchSession implements Closeable { } /** - * 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>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: * - * <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. + * <li>AND (implicit) + * <p>AND is an operator that matches documents that contain <i>all</i> provided terms. + * <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly + * including "AND" in a query string will treat "AND" as a term, returning documents that + * also contain "AND". + * <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and", + * "banana". + * <p>Example: "apple banana" matches documents that contain both "apple" and "banana". + * <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and + * "cherry". + * <li>OR + * <p>OR is an operator that matches documents that contain <i>any</i> provided term. + * <p>Example: "apple OR banana" matches documents that contain either "apple" or + * "banana". + * <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple", + * "banana", or "cherry". + * <li>Exclusion (-) + * <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the + * provided term. + * <p>Example: "-apple" matches documents that do not contain "apple". + * <li>Grouped Terms + * <p>For queries that require multiple operators and terms, terms can be grouped into + * subqueries. Subqueries are contained within an open "(" and close ")" parenthesis. + * <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either + * "donut" or "bagel" and either "coffee" or "tea". + * <li>Property Restricts + * <p>For queries that require a term to match a specific {@link AppSearchSchema} property + * of a document, a ":" must be included between the property name and the term. + * <p>Example: "subject:important" matches documents that contain the term "important" in + * the "subject" property. * </ul> * - * <p> This method is lightweight. The heavy work will be done in - * {@link SearchResults#getNextPage}. + * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or + * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter. * - * @param queryExpression Query String to search. - * @param searchSpec Spec for setting filters, raw query etc. + * <p>This method is lightweight. The heavy work will be done in {@link + * SearchResults#getNextPage}. + * + * @param queryExpression query string to search. + * @param searchSpec spec for setting document filters, adding projection, setting term match + * type, etc. * @param executor Executor on which to invoke the callback of the following request * {@link SearchResults#getNextPage}. - * @return The search result of performing this operation. + * @return a {@link SearchResults} object for retrieved matched documents. */ @NonNull public SearchResults search( @@ -440,8 +487,8 @@ public final class AppSearchSession implements Closeable { * * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency * metrics for that particular document. These metrics are used for ordering {@link #search} - * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and - * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. + * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link + * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. * * <p>Reporting usage of a document is optional. * @@ -479,9 +526,17 @@ public final class AppSearchSession implements Closeable { } /** - * Removes {@link GenericDocument}s from the index by URI. + * Removes {@link GenericDocument} objects by URIs and namespace from the {@link + * AppSearchSession} database. + * + * <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 Request containing URIs to be removed. + * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. The keys * of the returned {@link AppSearchBatchResult} are the input URIs. The values @@ -520,19 +575,18 @@ public final class AppSearchSession implements Closeable { /** * 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 86518347d770..09bca4fb3b9d 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 @@ import java.util.function.Consumer; /** * 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 @@ public class GlobalSearchSession implements 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: - * <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 704509bce2a1..a63e01555f6c 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 @@ import java.util.function.Consumer; /** * 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 @@ public class SearchResults implements Closeable { /** * 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 e94b3b269041..8bf438d43cd1 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 @@ public final class AppSearchSchema { * <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 4c11514cf403..138eb23148af 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 @@ import java.util.Set; /** * 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 @@ public class GenericDocument { /** 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 @@ public class GenericDocument { 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 @@ public class GenericDocument { } /** - * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { } /** - * 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 @@ public class GenericDocument { private boolean mBuilt = false; /** - * Create a new {@link GenericDocument.Builder}. + * Creates a new {@link GenericDocument.Builder}. + * + * <p>Once {@link #build} is called, the instance can no longer be used. * - * @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}. + * @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 @@ public class GenericDocument { } /** - * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { /** * 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 @@ public class GenericDocument { } /** - * 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 @@ public class GenericDocument { /** * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { /** * 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 @@ public class GenericDocument { /** * 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 @@ public class GenericDocument { /** * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { } } - /** 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 656608d82ad4..c927e3412cbc 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.Map; 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 @@ public final class GetByUriRequest { 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 @@ public final class GetByUriRequest { 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 @@ public final class GetByUriRequest { 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 called, the namespace defaults to {@link + * GenericDocument#DEFAULT_NAMESPACE}. * - * <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) { @@ -120,14 +128,22 @@ public final class GetByUriRequest { 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 @@ public final class GetByUriRequest { * 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 @@ public final class GetByUriRequest { * 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 @@ public final class GetByUriRequest { 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 198eee85be53..39b53b604abb 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.Collections; 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 @@ public final class RemoveByUriRequest { 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 @@ public final class RemoveByUriRequest { 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 @@ public final class RemoveByUriRequest { 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 4869aa38b5fd..bc99d4f67d86 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 @@ import java.util.Set; /** The response class of {@link AppSearchSession#setSchema} */ public class SetSchemaResponse { + + 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; - 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) { + + /** 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 @@ public class SetSchemaResponse { */ @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 @@ public class SetSchemaResponse { */ @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 @@ public class SetSchemaResponse { */ @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 @@ public class SetSchemaResponse { * @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 @@ public class SetSchemaResponse { 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 @@ public class SetSchemaResponse { 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 @@ public class SetSchemaResponse { 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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { * {@link AppSearchSession#setSchema}. */ public static class MigrationFailure { - private final String mSchemaType; - private final String mNamespace; - private final String mUri; - AppSearchResult<Void> mFailureResult; - - MigrationFailure( - @NonNull String schemaType, - @NonNull String namespace, - @NonNull String uri, - @NonNull AppSearchResult<Void> result) { - mSchemaType = schemaType; - mNamespace = namespace; - mUri = uri; - mFailureResult = result; + 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"; + + 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 @@ public class SetSchemaResponse { */ @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 f04ace684839..000000000000 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.appsearch; - -import android.annotation.NonNull; -import android.os.Bundle; - -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This class represents the results of setSchema(). - * - * @hide - */ -public class SetSchemaResult { - - public static final String DELETED_SCHEMA_TYPES_FIELD = "deletedSchemaTypes"; - public static final String INCOMPATIBLE_SCHEMA_TYPES_FIELD = "incompatibleSchemaTypes"; - public static final String RESULT_CODE_FIELD = "resultCode"; - private final List<String> mDeletedSchemaTypes; - private final List<String> mIncompatibleSchemaTypes; - private final Bundle mBundle; - - SetSchemaResult(@NonNull Bundle bundle) { - mBundle = Preconditions.checkNotNull(bundle); - mDeletedSchemaTypes = - Preconditions.checkNotNull(mBundle.getStringArrayList(DELETED_SCHEMA_TYPES_FIELD)); - mIncompatibleSchemaTypes = - Preconditions.checkNotNull( - mBundle.getStringArrayList(INCOMPATIBLE_SCHEMA_TYPES_FIELD)); - } - - /** Returns the {@link Bundle} of this class. */ - @NonNull - public Bundle getBundle() { - return mBundle; - } - - /** returns all deleted schema types in this setSchema call. */ - @NonNull - public List<String> getDeletedSchemaTypes() { - return Collections.unmodifiableList(mDeletedSchemaTypes); - } - - /** returns all incompatible schema types in this setSchema call. */ - @NonNull - public List<String> getIncompatibleSchemaTypes() { - return Collections.unmodifiableList(mIncompatibleSchemaTypes); - } - - /** - * returns the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link - * AppSearchSession#setSchema} call. - */ - public int getResultCode() { - return mBundle.getInt(RESULT_CODE_FIELD); - } - - /** Builder for {@link SetSchemaResult} objects. */ - public static final class Builder { - private final ArrayList<String> mDeletedSchemaTypes = new ArrayList<>(); - private final ArrayList<String> mIncompatibleSchemaTypes = new ArrayList<>(); - @AppSearchResult.ResultCode private int mResultCode; - private boolean mBuilt = false; - - /** Adds a deletedSchemaTypes to the {@link SetSchemaResult}. */ - @NonNull - public Builder addDeletedSchemaType(@NonNull String deletedSchemaType) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - mDeletedSchemaTypes.add(Preconditions.checkNotNull(deletedSchemaType)); - return this; - } - - /** Adds a incompatible SchemaTypes to the {@link SetSchemaResult}. */ - @NonNull - public Builder addIncompatibleSchemaType(@NonNull String incompatibleSchemaTypes) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - mIncompatibleSchemaTypes.add(Preconditions.checkNotNull(incompatibleSchemaTypes)); - return this; - } - - /** - * Sets the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link - * AppSearchSession#setSchema} call to the {@link SetSchemaResult} - */ - @NonNull - public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - mResultCode = resultCode; - return this; - } - - /** Builds a {@link SetSchemaResult}. */ - @NonNull - public SetSchemaResult build() { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Bundle bundle = new Bundle(); - bundle.putStringArrayList( - SetSchemaResult.DELETED_SCHEMA_TYPES_FIELD, mDeletedSchemaTypes); - bundle.putStringArrayList( - SetSchemaResult.INCOMPATIBLE_SCHEMA_TYPES_FIELD, mIncompatibleSchemaTypes); - bundle.putInt(RESULT_CODE_FIELD, mResultCode); - mBuilt = true; - return new SetSchemaResult(bundle); - } - } -} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 2f1817ec82a7..6c2e30e98877 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.GetByUriRequest; 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.ResultCodeTo 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.StatusProto; 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 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * @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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { } 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { * @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 @@ public final class AppSearchImpl { throws AppSearchException { mReadWriteLock.writeLock().lock(); try { + throwIfClosedLocked(); + SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder(); SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder(); @@ -297,9 +336,16 @@ public final class AppSearchImpl { try { checkSuccess(setSchemaResultProto.getStatus()); } catch (AppSearchException e) { - if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 - || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) { - return SetSchemaResultToProtoConverter.toSetSchemaResult( + // Swallow the exception for the incompatible change case. We will propagate + // those deleted schemas and incompatible types to the SetSchemaResponse. + boolean isFailedPrecondition = + setSchemaResultProto.getStatus().getCode() + == StatusProto.Code.FAILED_PRECONDITION; + boolean isIncompatible = + setSchemaResultProto.getDeletedSchemaTypesCount() > 0 + || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0; + if (isFailedPrecondition && isIncompatible) { + return SetSchemaResponseToProtoConverter.toSetSchemaResponse( setSchemaResultProto, prefix); } else { throw e; @@ -328,16 +374,8 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { @NonNull public List<AppSearchSchema> getSchema( @NonNull String packageName, @NonNull String databaseName) throws AppSearchException { - SchemaProto fullSchema; mReadWriteLock.readLock().lock(); try { - fullSchema = getSchemaProtoLocked(); - } finally { - mReadWriteLock.readLock().unlock(); - } + throwIfClosedLocked(); - 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); + SchemaProto fullSchema = getSchemaProtoLocked(); - // 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); + 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); + AppSearchSchema schema = + SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder); + result.add(schema); + } + return result; + } finally { + mReadWriteLock.readLock().unlock(); } - return result; } /** @@ -410,23 +451,22 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { 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 final class AppSearchImpl { 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 final class AppSearchImpl { public void invalidateNextPageToken(long nextPageToken) { mReadWriteLock.readLock().lock(); try { + throwIfClosedLocked(); + mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken); } finally { mReadWriteLock.readLock().unlock(); @@ -688,16 +738,19 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { @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 @@ public final class AppSearchImpl { 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 @@ public final class AppSearchImpl { * @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 @@ public final class AppSearchImpl { * * @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(); - - // Must be called after everything else since VisibilityStore may repopulate - // IcingSearchEngine with an initial schema. - mVisibilityStoreLocked.handleReset(); - } finally { - mReadWriteLock.writeLock().unlock(); - } + @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(); checkSuccess(resetResultProto.getStatus()); } @@ -1079,7 +1140,8 @@ public final class AppSearchImpl { * <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 @@ public final class AppSearchImpl { /** * 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 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. + */ + 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. * - * @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}. + * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link + * GetOptimizeInfoResultProto} shows there is enough resources could be released. */ - @GuardedBy("mReadWriteLock") - private void checkForOptimizeLocked(boolean force) throws AppSearchException { - ++mOptimizeIntervalCountLocked; - if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) { - mOptimizeIntervalCountLocked = 0; + 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 b3f8264dc18a..a501e99db1ef 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 @@ class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper { 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 @@ class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper { 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 index e1e7d46d77ea..a0f39ecd68fb 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 6ba557224582..d076db3b8f82 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -I2bf8bd9db1b71b7da4ab50dd7480e4529678413a +Ia9a8daef1a6d7d9432f7808d440abd64f4797701 diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java index b2ffd5b4b60c..ea21e19b2bea 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java @@ -77,10 +77,14 @@ public interface AppSearchSessionShim extends Closeable { * android.app.appsearch.exceptions.AppSearchException} with the {@link * AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not * compatible with the new schema will be deleted and the incompatible schema will be - * applied. + * applied. Incompatible types and deleted types will be set into {@link + * SetSchemaResponse#getIncompatibleTypes()} and {@link + * SetSchemaResponse#getDeletedTypes()}, respectively. * <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type * and make no deletion. The migrator will migrate documents from it's old schema version - * to the new version. See the migration section below. + * to the new version. Migrated types will be set into both {@link + * SetSchemaResponse#getIncompatibleTypes()} and {@link + * SetSchemaResponse#getMigratedTypes()}. See the migration section below. * </ul> * * <p>It is a no-op to set the same schema as has been previously set; this is handled @@ -109,10 +113,8 @@ public interface AppSearchSessionShim extends Closeable { * backwards-compatible, and stored documents won't have any observable changes. * * @param request The schema update request. - * @return The pending {@link SetSchemaResponse} of performing this operation. Success if the - * the schema has been set and any migrations has been done. Otherwise, the failure {@link - * android.app.appsearch.SetSchemaResponse.MigrationFailure} indicates which document is - * fail to be migrated. + * @return A {@link ListenableFuture} with exception if we hit any error. Or the pending {@link + * SetSchemaResponse} of performing this operation, if the schema has been successfully set. * @see android.app.appsearch.AppSearchSchema.Migrator * @see android.app.appsearch.AppSearchMigrationHelper.Transformer */ @@ -144,57 +146,79 @@ public interface AppSearchSessionShim extends Closeable { 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>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>Currently we support following features in the raw query format: + * <p>For query strings with a single term and no operators, documents that match the provided + * query string and {@link SearchSpec} will be returned. + * + * <p>The following operators are supported: * * <ul> - * <li>AND - * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”). - * Example: hello world matches documents that have both ‘hello’ and ‘world’ + * <li>AND (implicit) + * <p>AND is an operator that matches documents that contain <i>all</i> provided terms. + * <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly + * including "AND" in a query string will treat "AND" as a term, returning documents that + * also contain "AND". + * <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and", + * "banana". + * <p>Example: "apple banana" matches documents that contain both "apple" and "banana". + * <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and + * "cherry". * <li>OR - * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example: - * dog OR puppy - * <li>Exclusion - * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example: - * -dog excludes the term ‘dog’ - * <li>Grouping terms - * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. - * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). - * Example: (dog puppy) (cat kitten) two one group containing two terms. - * <li>Property restricts - * <p>Specifies which properties of a document to specifically match terms in (e.g. “match - * documents where the ‘subject’ property contains ‘important’”). Example: - * subject:important matches documents with the term ‘important’ in the ‘subject’ property - * <li>Schema type restricts - * <p>This is similar to property restricts, but allows for restricts on top-level - * document fields, such as schema_type. Clients should be able to limit their query to - * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’ - * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will - * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema - * type or the ‘Video’ schema type. + * <p>OR is an operator that matches documents that contain <i>any</i> provided term. + * <p>Example: "apple OR banana" matches documents that contain either "apple" or + * "banana". + * <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple", + * "banana", or "cherry". + * <li>Exclusion (-) + * <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the + * provided term. + * <p>Example: "-apple" matches documents that do not contain "apple". + * <li>Grouped Terms + * <p>For queries that require multiple operators and terms, terms can be grouped into + * subqueries. Subqueries are contained within an open "(" and close ")" parenthesis. + * <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either + * "donut" or "bagel" and either "coffee" or "tea". + * <li>Property Restricts + * <p>For queries that require a term to match a specific {@link AppSearchSchema} property + * of a document, a ":" must be included between the property name and the term. + * <p>Example: "subject:important" matches documents that contain the term "important" in + * the "subject" property. * </ul> * + * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or + * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter. + * * <p>This method is lightweight. The heavy work will be done in {@link * SearchResultsShim#getNextPage()}. * - * @param queryExpression Query String to search. - * @param searchSpec Spec for setting filters, raw query etc. - * @return The search result of performing this operation. + * @param queryExpression query string to search. + * @param searchSpec spec for setting document filters, adding projection, setting term match + * type, etc. + * @return a {@link SearchResultsShim} object for retrieved matched documents. */ @NonNull SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); @@ -219,13 +243,22 @@ public interface AppSearchSessionShim extends Closeable { 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 44d5180c3a36..37717d6837b9 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.GenericDocument; 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 @@ public class AppSearchTestUtils { 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 b96f99e7cd37..31c934f8bb27 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 @@ import java.io.Closeable; */ public interface GlobalSearchSessionShim extends Closeable { /** - * Searches across all documents in the storage based on a given query string. + * Retrieves documents from all AppSearch databases that the querying application has access to. * - * <p>Currently we support following features in the raw query format: + * <p>Applications can be granted access to documents by specifying {@link + * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}, or {@link + * SetSchemaRequest.Builder#setDocumentClassVisibilityForPackage} when building a schema. * - * <ul> - * <li>AND - * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”). - * Example: hello world matches documents that have both ‘hello’ and ‘world’ - * <li>OR - * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example: - * dog OR puppy - * <li>Exclusion - * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example: - * -dog excludes the term ‘dog’ - * <li>Grouping terms - * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. - * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). - * Example: (dog puppy) (cat kitten) two one group containing two terms. - * <li>Property restricts - * <p>Specifies which properties of a document to specifically match terms in (e.g. “match - * documents where the ‘subject’ property contains ‘important’”). Example: - * subject:important matches documents with the term ‘important’ in the ‘subject’ property - * <li>Schema type restricts - * <p>This is similar to property restricts, but allows for restricts on top-level - * document fields, such as schema_type. Clients should be able to limit their query to - * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’ - * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will - * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema - * type or the ‘Video’ schema type. - * </ul> + * <p>Document access can also be granted to system UIs by specifying {@link + * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi}, or {@link + * SetSchemaRequest.Builder#setDocumentClassVisibilityForSystemUi} when building a schema. + * + * <p>See {@link AppSearchSessionShim#search(String, SearchSpec)} for a detailed explanation on + * forming a query string. * * <p>This method is lightweight. The heavy work will be done in {@link * SearchResultsShim#getNextPage()}. * - * @param queryExpression Query String to search. - * @param searchSpec Spec for setting filters, raw query etc. - * @return The search result of performing this operation. + * @param queryExpression query string to search. + * @param searchSpec spec for setting document filters, adding projection, setting term match + * type, etc. + * @return a {@link SearchResultsShim} object for retrieved matched documents. */ @NonNull SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); diff --git a/apex/media/Android.bp b/apex/media/Android.bp index 5f1bd374df00..d6666f521b17 100644 --- a/apex/media/Android.bp +++ b/apex/media/Android.bp @@ -18,3 +18,10 @@ package { "//frameworks/av/apex/testing", ], } + +sdk { + name: "media-module-sdk", + java_sdk_libs: [ + "framework-media", + ], +} diff --git a/api/Android.bp b/api/Android.bp index d5c6bf6d024e..ac2f0831353c 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -37,6 +37,7 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":framework-mediaprovider{.module-lib.removed-api.txt}", ":framework-permission{.module-lib.removed-api.txt}", ":framework-permission-s{.module-lib.removed-api.txt}", + ":framework-scheduling{.module-lib.removed-api.txt}", ":framework-sdkextensions{.module-lib.removed-api.txt}", ":framework-statsd{.module-lib.removed-api.txt}", ":framework-tethering{.module-lib.removed-api.txt}", diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 227b9e24f173..115c1f23c521 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 @@ public class UsbCommand extends Svc.Command { + " 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 @@ public class UsbCommand extends Svc.Command { System.err.println("Error communicating with UsbManager: " + e); } return; + } else if ("getUsbHalVersion".equals(args[1])) { + try { + int version = usbMgr.getUsbHalVersion(); + + if (version == 13) { + System.err.println("V1_3"); + } else if (version == 12) { + System.err.println("V1_2"); + } else if (version == 11) { + System.err.println("V1_1"); + } else if (version == 10) { + System.err.println("V1_0"); + } else { + System.err.println("unknown"); + } + } catch (RemoteException e) { + System.err.println("Error communicating with UsbManager: " + e); + } + return; } } System.err.println(longHelp()); diff --git a/core/api/current.txt b/core/api/current.txt index 38783abbe34b..c2a5a27af35a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1186,6 +1186,7 @@ package android { 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 @@ -1296,6 +1297,7 @@ package android { 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 @@ -1615,6 +1617,7 @@ package android { 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 @@ -1655,6 +1658,10 @@ package android { 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 @@ -1741,6 +1748,9 @@ package android { 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 } @@ -3865,6 +3875,7 @@ package android.app { 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(); @@ -6653,6 +6664,7 @@ package android.app { 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); @@ -6951,6 +6963,7 @@ package android.app.admin { 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); @@ -7078,6 +7091,7 @@ package android.app.admin { 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); @@ -7179,6 +7193,7 @@ package android.app.admin { 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); @@ -8295,6 +8310,7 @@ package android.appwidget { 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); @@ -8302,11 +8318,13 @@ package android.appwidget { 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 { @@ -8359,6 +8377,7 @@ package android.appwidget { 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 { @@ -9820,6 +9839,7 @@ package android.content { 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; @@ -10226,12 +10246,15 @@ package android.content { 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); @@ -12778,6 +12801,7 @@ package android.content.pm { 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 { @@ -16515,9 +16539,13 @@ package android.graphics.drawable { 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 { @@ -24047,13 +24075,49 @@ package android.media.effect { package android.media.metrics { + public abstract class Event { + ctor protected Event(long); + method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis(); + } + public class MediaMetricsManager { method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession(); + field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL } public final class PlaybackSession implements java.lang.AutoCloseable { method public void close(); method @NonNull public String getId(); + method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent); + } + + public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable { + method public int describeContents(); + method public int getState(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackStateEvent> CREATOR; + field public static final int STATE_ABANDONED = 15; // 0xf + field public static final int STATE_BUFFERING = 6; // 0x6 + field public static final int STATE_ENDED = 11; // 0xb + field public static final int STATE_FAILED = 13; // 0xd + field public static final int STATE_INTERRUPTED_BY_AD = 14; // 0xe + field public static final int STATE_JOINING_BACKGROUND = 1; // 0x1 + field public static final int STATE_JOINING_FOREGROUND = 2; // 0x2 + field public static final int STATE_NOT_STARTED = 0; // 0x0 + field public static final int STATE_PAUSED = 4; // 0x4 + field public static final int STATE_PAUSED_BUFFERING = 7; // 0x7 + field public static final int STATE_PLAYING = 3; // 0x3 + field public static final int STATE_SEEKING = 5; // 0x5 + field public static final int STATE_STOPPED = 12; // 0xc + field public static final int STATE_SUPPRESSED = 9; // 0x9 + field public static final int STATE_SUPPRESSED_BUFFERING = 10; // 0xa + } + + public static final class PlaybackStateEvent.Builder { + ctor public PlaybackStateEvent.Builder(); + method @NonNull public android.media.metrics.PlaybackStateEvent build(); + method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setState(int); + method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); } } @@ -49366,6 +49430,7 @@ package android.view { 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); @@ -54625,6 +54690,7 @@ package android.widget { 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); @@ -55822,6 +55888,25 @@ package android.widget.inline { } +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 4e256254c04a..b57fdf171ce7 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -73,6 +73,7 @@ package android.hardware.usb { 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 @@ package android.hardware.usb { 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 @@ package android.net { 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 086325cfbfb8..754185345c9d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -27,6 +27,7 @@ package android { 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"; @@ -246,6 +247,7 @@ package android { 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"; @@ -342,6 +344,7 @@ package android { 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 { @@ -679,6 +682,7 @@ package android.app { } 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 } @@ -871,7 +875,6 @@ package android.app.admin { } 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(); @@ -1851,6 +1854,7 @@ package android.bluetooth { } 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(); @@ -2119,6 +2123,7 @@ package android.content { field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; + field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness"; field public static final String ROLLBACK_SERVICE = "rollback"; field public static final String SEARCH_UI_SERVICE = "search_ui"; field public static final String SECURE_ELEMENT_SERVICE = "secure_element"; @@ -2814,16 +2819,49 @@ package android.debug { package android.graphics.fonts { + public final class FontFamilyUpdateRequest { + method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.FontFamily> getFontFamilies(); + method @NonNull public java.util.List<android.graphics.fonts.FontFileUpdateRequest> getFontFileUpdateRequests(); + } + + public static final class FontFamilyUpdateRequest.Builder { + ctor public FontFamilyUpdateRequest.Builder(); + method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.FontFamily); + method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFileUpdateRequest(@NonNull android.graphics.fonts.FontFileUpdateRequest); + method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest build(); + } + + public static final class FontFamilyUpdateRequest.Font { + ctor public FontFamilyUpdateRequest.Font(@NonNull String, @NonNull android.graphics.fonts.FontStyle, @NonNull java.util.List<android.graphics.fonts.FontVariationAxis>); + method @NonNull public java.util.List<android.graphics.fonts.FontVariationAxis> getAxes(); + method @NonNull public String getPostScriptName(); + method @NonNull public android.graphics.fonts.FontStyle getStyle(); + } + + public static final class FontFamilyUpdateRequest.FontFamily { + ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>); + method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts(); + method @NonNull public String getName(); + } + + public final class FontFileUpdateRequest { + ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]); + method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor(); + method @NonNull public byte[] getSignature(); + } + public class FontManager { method @Nullable public android.text.FontConfig getFontConfig(); - method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); + method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa + field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7 field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9 field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc - field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7 field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8 field public static final int RESULT_SUCCESS = 0; // 0x0 @@ -3506,6 +3544,7 @@ package android.hardware.location { 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 @@ -7340,6 +7379,7 @@ package android.net { 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 @@ -7354,6 +7394,7 @@ package android.net { 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[]); @@ -8527,7 +8568,7 @@ package android.os { 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); @@ -8597,6 +8638,7 @@ package android.os { 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 { @@ -10385,6 +10427,7 @@ package android.service.storage { 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 @@ -11076,6 +11119,8 @@ package android.telephony { } 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"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 694507db6fe7..86fe8c30ca22 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -837,14 +837,16 @@ package android.graphics.fonts { public class FontManager { method @Nullable public android.text.FontConfig getFontConfig(); - method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); + method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa + field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7 field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9 field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc - field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7 field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8 field public static final int RESULT_SUCCESS = 0; // 0x0 @@ -1348,6 +1350,12 @@ package android.net { 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); } @@ -2723,6 +2731,10 @@ package android.window { 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(); @@ -2742,6 +2754,7 @@ package android.window { 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 3c67e44ff41f..87fb5b1b3b2e 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -487,6 +487,8 @@ GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): 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/app/Activity.java b/core/java/android/app/Activity.java index 4728f11a402d..992d054737b9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -139,6 +139,8 @@ import android.view.translation.UiTranslationController; 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 @@ public class Activity extends ContextThemeWrapper private UiTranslationController mUiTranslationController; + private SplashScreen mSplashScreen; + /** @hide */ + SplashScreenView mSplashScreenView; + private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { /** @@ -1603,6 +1609,23 @@ public class Activity extends ContextThemeWrapper } /** + * 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 401f8cc13bad..e3b5e9a32324 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -46,9 +46,9 @@ public class ActivityClient { } /** 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 @@ public class ActivityClient { } } + /** + * 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 28da1c3a3eb7..73cc13c82bcb 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -160,6 +160,12 @@ public class ActivityOptions { 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 @@ public class ActivityOptions { 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 @@ public class ActivityOptions { 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 @@ public class ActivityOptions { } /** + * 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 @@ public class ActivityOptions { 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 70fa4445479b..233f737b8e0f 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -19,6 +19,7 @@ package android.app; 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.os.ServiceManager; 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 @@ public class ActivityTaskManager { } /** + * 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 bb6a774cbee2..3d9f6123963f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -171,12 +171,15 @@ import android.view.View; 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.content.ReferrerIntent; 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.Map; 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 @@ public final class ActivityThread extends ClientTransactionHandler { /** 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { } /** + * 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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 0e1c827145d2..cf5fd148c030 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; 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 @@ public abstract class ClientTransactionHandler { /** 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 9a20e0fefd33..85fb543a3967 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.lang.annotation.RetentionPolicy; 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 @@ class ContextImpl extends Context { } } + @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 @@ class ContextImpl extends Context { 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 9d3286fa271c..bb743b89e00f 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -34,7 +34,7 @@ import com.android.internal.policy.IKeyguardDismissCallback; */ 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 @@ interface IActivityClientController { * 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 e73636fe7fd0..4ad13e1932dd 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -220,6 +220,8 @@ interface IActivityManager { 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 38a3e70b3742..542f754ce364 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -70,6 +70,7 @@ import android.view.IRecentsAnimationRunner; 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 @@ interface IActivityTaskManager { * 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/ILocalWallpaperColorConsumer.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl new file mode 100644 index 000000000000..28b11ec04d96 --- /dev/null +++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.WallpaperColors; +import android.graphics.RectF; + +/** + * @hide + */ +oneway interface ILocalWallpaperColorConsumer { + void onColorsChanged(in RectF area, in WallpaperColors colors); +}
\ No newline at end of file diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 0ba5beccbf32..f71eebdc66ec 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -62,6 +62,16 @@ interface IUiModeManager { 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 101917bc2e07..5402381b7207 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 @@ interface IWallpaperManager { 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 b6fc47f27b05..77daf8ddf08f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1604,6 +1604,14 @@ public class Notification implements Parcelable @SystemApi public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; + /** + * {@code SemanticAction}: Mark content as a potential phishing attempt. + * Note that this is only for use by the notification assistant services. + * @hide + */ + @SystemApi + public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; + private final Bundle mExtras; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Icon mIcon; @@ -2315,7 +2323,8 @@ public class Notification implements Parcelable SEMANTIC_ACTION_THUMBS_UP, SEMANTIC_ACTION_THUMBS_DOWN, SEMANTIC_ACTION_CALL, - SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY + SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, + SEMANTIC_ACTION_CONVERSATION_IS_PHISHING }) @Retention(RetentionPolicy.SOURCE) public @interface SemanticAction {} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d5e95708a805..c47b546653b9 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -128,11 +128,13 @@ import android.net.EthernetManager; 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; @@ -183,6 +185,7 @@ import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.scheduling.SchedulingFrameworkInitializer; import android.security.FileIntegrityManager; import android.security.IFileIntegrityService; import android.service.oemlock.IOemLockService; @@ -384,6 +387,15 @@ public final class SystemServiceRegistry { 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 @@ -1429,6 +1441,7 @@ public final class SystemServiceRegistry { MediaFrameworkPlatformInitializer.registerServiceWrappers(); MediaFrameworkInitializer.registerServiceWrappers(); RoleFrameworkInitializer.registerServiceWrappers(); + SchedulingFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 938ce0d56933..9019ddf941d9 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -343,7 +343,9 @@ public class TaskInfo { // 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 e1c262caf44f..9b99ab8e31cb 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -477,11 +477,13 @@ public class UiModeManager { * 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 @@ public class UiModeManager { } /** + * 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 ac2f22357013..d7587bdce997 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -66,6 +66,7 @@ import android.os.RemoteException; 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 @@ public class WallpaperManager { 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 @@ public class WallpaperManager { 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 @@ public class WallpaperManager { } } + 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 @@ public class WallpaperManager { } /** + * @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 @@ public class WallpaperManager { onColorsChanged(colors, which); } } + + /** + * Callback to update a consumer with a local color change + * @hide + */ + public interface LocalWallpaperColorConsumer { + + /** + * Gets called when a color of an area gets updated + * @param area + * @param colors + */ + void onColorsChanged(RectF area, WallpaperColors colors); + } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ff6f6a046729..05e9dcf007e0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1300,10 +1300,11 @@ public class DevicePolicyManager { * 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 @@ -11717,8 +11718,11 @@ public class DevicePolicyManager { } /** - * 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: @@ -11758,7 +11762,7 @@ public class DevicePolicyManager { * @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 */ @@ -11772,14 +11776,16 @@ public class DevicePolicyManager { } /** - * 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"); @@ -11791,9 +11797,14 @@ public class DevicePolicyManager { } /** - * 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. * @@ -11805,11 +11816,11 @@ public class DevicePolicyManager { * 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. @@ -11817,8 +11828,9 @@ public class DevicePolicyManager { * @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 */ @@ -11937,11 +11949,12 @@ public class DevicePolicyManager { } /** - * 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. * @@ -13357,26 +13370,93 @@ public class DevicePolicyManager { */ public boolean canAdminGrantSensorsPermissions() { throwIfParentInstance("canAdminGrantSensorsPermissions"); - return canAdminGrantSensorsPermissionsForUser(myUserId()); + if (mService == null) { + return false; + } + try { + 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(); + } + } } /** - * Returns true if the admin can control grants of sensors-related permissions, for - * a given user. + * 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 - * @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; + public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) { + throwIfParentInstance("isUsbDataSignalingEnabledForUser"); + if (mService != null) { + try { + return mService.isUsbDataSignalingEnabledForUser(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - try { - return mService.canAdminGrantSensorsPermissionsForUser(userId); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + return true; + } + + /** + * Returns whether enabling or disabling USB data signaling is supported on the device. + * + * @return {@code true} if the device supports enabling and disabling USB data signaling. + */ + public boolean canUsbDataSignalingBeDisabled() { + throwIfParentInstance("canUsbDataSignalingBeDisabled"); + if (mService != null) { + try { + return mService.canUsbDataSignalingBeDisabled(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } + return false; } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 032cf2483cd3..94388cfd41b9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -502,4 +502,9 @@ interface IDevicePolicyManager { 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/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 7e232acbdcdd..85cfe835c28d 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -401,7 +401,8 @@ public abstract class BackupAgent extends ContextWrapper { * @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 @@ public abstract class BackupAgent extends ContextWrapper { 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 @@ public abstract class BackupAgent extends ContextWrapper { 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 @@ public abstract class BackupAgent extends ContextWrapper { 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 @@ public abstract class BackupAgent extends ContextWrapper { + " 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 742d05c1ffa4..f7ed6f1f2feb 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.FileInputStream; 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 @@ public class FullBackup { "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 @@ public class FullBackup { 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 @@ public class FullBackup { */ 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 28b73406b877..ab38832458d6 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,8 +20,16 @@ import android.annotation.NonNull; 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 @@ public final class CompatChanges { 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 000000000000..9f97cd41128a --- /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 d451599cc7b0..e6fdc006615a 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -60,7 +60,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { 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 000000000000..5374984d31d0 --- /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/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index a3c3a0e106a3..42d90a794e74 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.LauncherApps; 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 android.widget.TextView; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; /** @@ -89,6 +92,7 @@ public class AppWidgetHostView extends FrameLayout { int mLayoutId = -1; private OnClickHandler mOnClickHandler; private boolean mOnLightBackground; + PointF mCurrentSize = null; private Executor mAsyncExecutor; private CancellationSignal mLastExecutionSignal; @@ -268,7 +272,8 @@ public class AppWidgetHostView extends FrameLayout { * 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 @@ public class AppWidgetHostView extends FrameLayout { * @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 @@ public class AppWidgetHostView extends FrameLayout { 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 @@ public class AppWidgetHostView extends FrameLayout { // 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 @@ public class AppWidgetHostView extends FrameLayout { mView, mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, true), - mOnClickHandler); + mOnClickHandler, + mCurrentSize); } catch (Exception e) { // Reapply failed. Try apply } @@ -502,7 +593,8 @@ public class AppWidgetHostView extends FrameLayout { this, mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, false), - mOnClickHandler); + mOnClickHandler, + mCurrentSize); } } @@ -533,7 +625,8 @@ public class AppWidgetHostView extends FrameLayout { 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 37093a10b2f0..aac8710e8691 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -217,6 +217,12 @@ public class AppWidgetManager { 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 ea7e5ea7c802..ec46da0dcf0e 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1654,7 +1654,7 @@ public final class BluetoothAdapter { 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 3b8dec7bf955..e7661dbad749 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1236,7 +1236,8 @@ public final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { } /** + * 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 018863774184..f3ecbf6c08f3 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -740,6 +740,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList<Item>(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -756,6 +757,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList<Item>(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -914,6 +916,9 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mItems.add(item); + if (mItems.size() == 1) { + mClipDescription.setIsStyledText(isStyledText()); + } } /** @@ -1049,6 +1054,20 @@ public class ClipData implements Parcelable { } } + 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 e3395e20947d..d48f83223d3e 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -120,6 +120,7 @@ public class ClipDescription implements Parcelable { private final ArrayList<String> mMimeTypes; private PersistableBundle mExtras; private long mTimeStamp; + private boolean mIsStyledText; /** * Create a new clip. @@ -325,6 +326,26 @@ public class ClipDescription implements Parcelable { } } + /** + * 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 @@ public class ClipDescription implements Parcelable { dest.writeStringList(mMimeTypes); dest.writePersistableBundle(mExtras); dest.writeLong(mTimeStamp); + dest.writeBoolean(mIsStyledText); } ClipDescription(Parcel in) { @@ -436,6 +458,7 @@ public class ClipDescription implements Parcelable { 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 025d777f2053..51669432e377 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -93,6 +93,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.concurrent.Executor; /** @@ -3520,6 +3521,7 @@ public abstract class Context { APPWIDGET_SERVICE, //@hide: VOICE_INTERACTION_MANAGER_SERVICE, //@hide: BACKUP_SERVICE, + REBOOT_READINESS_SERVICE, ROLLBACK_SERVICE, DROPBOX_SERVICE, //@hide: DEVICE_IDLE_CONTROLLER, @@ -4742,6 +4744,17 @@ public abstract class Context { public static final String ROLLBACK_SERVICE = "rollback"; /** + * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.app.scheduling.RebootReadinessManagerService} for communicating + * with the reboot readiness detector. + * + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String REBOOT_READINESS_SERVICE = "reboot_readiness"; + + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.os.DropBoxManager} instance for recording * diagnostic logs. @@ -5725,6 +5738,32 @@ public abstract class Context { public abstract int checkUriPermission(Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags); + /** + * Determine whether a particular process and user ID has been granted + * permission to access a list of URIs. This only checks for permissions + * that have been explicitly granted -- if the given process/uid has + * more general access to the URI's content provider then this check will + * always fail. + * + * @param uris The list of URIs that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The access modes to check for the list of uris + * + * @return Array of permission grants corresponding to each entry in the list of uris. + * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri, + * or {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkCallingUriPermission + */ + @NonNull + @PackageManager.PermissionResult + public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid, + @Intent.AccessUriMode int modeFlags) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** @hide */ @SuppressWarnings("HiddenAbstractMethod") @PackageManager.PermissionResult @@ -5755,6 +5794,32 @@ public abstract class Context { public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags); /** + * Determine whether the calling process and user ID has been + * granted permission to access a list of URIs. This is basically + * the same as calling {@link #checkUriPermissions(List, int, int, int)} + * with the pid and uid returned by {@link + * android.os.Binder#getCallingPid} and {@link + * android.os.Binder#getCallingUid}. One important difference is + * that if you are not currently processing an IPC, this function + * will always fail. + * + * @param uris The list of URIs that is being checked. + * @param modeFlags The access modes to check. + * + * @return Array of permission grants corresponding to each entry in the list of uris. + * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri, + * or {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkUriPermission(Uri, int, int, int) + */ + @NonNull + @PackageManager.PermissionResult + public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, + @Intent.AccessUriMode int modeFlags) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Determine whether the calling process of an IPC <em>or you</em> has been granted * permission to access a specific URI. This is the same as * {@link #checkCallingUriPermission}, except it grants your own permissions @@ -5775,6 +5840,28 @@ public abstract class Context { @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 c1c213eee81b..b71fb2712c24 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -53,6 +53,7 @@ import java.io.FileNotFoundException; 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 @@ public class ContextWrapper extends Context { 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 @@ public class ContextWrapper extends Context { return mBase.checkCallingUriPermission(uri, modeFlags); } + @NonNull + @Override + public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) { + return mBase.checkCallingUriPermissions(uris, modeFlags); + } + @Override public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { return mBase.checkCallingOrSelfUriPermission(uri, modeFlags); } + @NonNull + @Override + public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) { + return mBase.checkCallingOrSelfUriPermissions(uris, modeFlags); + } + @Override public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, int modeFlags) { diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 85549d854c6d..ebe202b2a3fa 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -273,7 +273,7 @@ public class AppSearchShortcutInfo extends GenericDocument { 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/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 522f4ca88519..ce0547f5d0f4 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -434,6 +434,8 @@ public final class ShortcutInfo implements Parcelable { private int mDisabledReason; + private int mStartingThemeResId; + private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -462,6 +464,7 @@ public final class ShortcutInfo implements Parcelable { mLocusId = b.mLocusId; updateTimestamp(); + mStartingThemeResId = b.mStartingThemeResId; } /** @@ -608,6 +611,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } + mStartingThemeResId = source.mStartingThemeResId; } /** @@ -931,6 +935,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mLocusId != null) { mLocusId = source.mLocusId; } + if (source.mStartingThemeResId != 0) { + mStartingThemeResId = source.mStartingThemeResId; + } } /** @@ -1000,6 +1007,8 @@ public final class ShortcutInfo implements Parcelable { private LocusId mLocusId; + private int mStartingThemeResId; + /** * Old style constructor. * @hide @@ -1102,6 +1111,15 @@ public final class ShortcutInfo implements Parcelable { } /** + * 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { mPersons = source.readParcelableArray(cl, Person.class); mLocusId = source.readParcelable(cl); mIconUri = source.readString8(); + mStartingThemeResId = source.readInt(); } @Override @@ -2189,6 +2216,7 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 c62767ee031b..233abf36131b 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -71,6 +71,13 @@ public abstract class ShortcutServiceInternal { 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 bf35c4d92d3a..bc5d14a533e2 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -22,17 +22,26 @@ import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION; 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 @@ import java.util.Map; * @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 @@ public class DexMetadataHelper { /** * 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 @@ public class DexMetadataHelper { } } + /** Ensure that packageName and versionCode match the manifest.json in the .dm file */ + private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile, + String packageName, long versionCode, boolean requireManifest) + throws IOException, PackageParserException { + if (!requireManifest) { + if (DEBUG) { + Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + + " manifest.json check skipped"); + } + return; + } + + ZipEntry zipEntry = jarFile.findEntry("manifest.json"); + if (zipEntry == null) { + throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, + "Missing manifest.json in " + dmaPath); + } + InputStream inputStream = jarFile.getInputStream(zipEntry); + + JsonReader reader; + try { + reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, + "Error opening manifest.json in " + dmaPath, e); + } + String jsonPackageName = null; + long jsonVersionCode = -1; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("packageName")) { + jsonPackageName = reader.nextString(); + } else if (name.equals("versionCode")) { + jsonVersionCode = reader.nextLong(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + + if (jsonPackageName == null || jsonVersionCode == -1) { + throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, + "manifest.json in " + dmaPath + + " is missing 'packageName' and/or 'versionCode'"); + } + + if (!jsonPackageName.equals(packageName)) { + throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, + "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName + + ", expected: " + packageName); + } + + if (versionCode != jsonVersionCode) { + throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, + "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode + + ", expected: " + versionCode); + } + + if (DEBUG) { + Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName + + ", " + versionCode + ": successful"); + } + } + /** * Validates that all dex metadata paths in the given list have a matching apk. * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file). diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index af12536fff99..cbb3baaa6700 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 @@ public interface DomainVerificationManager { * 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 interface DomainVerificationManager { 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 @@ public interface DomainVerificationManager { 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 8d16f75bf1b4..73346ef0273b 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.Set; 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 @@ public final class DomainVerificationUserSelection implements Parcelable { // 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 @@ public final class DomainVerificationUserSelection implements Parcelable { @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 @@ public final class DomainVerificationUserSelection implements Parcelable { }; @DataClass.Generated( - time = 1611799495498L, + time = 1612829797220L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java", inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java new file mode 100644 index 000000000000..25758e9d9a61 --- /dev/null +++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.fonts; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Request for updating or adding a font family on the system. + * + * <p>You can update or add a font family with custom style parameters. The following example + * defines a font family called "roboto" using "Roboto-Regular" font file that is already available + * on the system by preloading or {@link FontManager#updateFontFile}. + * <pre> + * FontManager fm = getContext().getSystemService(FontManager.class); + * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder() + * .addFontFamily(new FontFamilyUpdateRequest.FontFamily("roboto", Arrays.asList( + * new FontFamilyUpdateRequest.Font( + * "Roboto-Regular", + * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), + * Collections.emptyList()), + * new FontFamilyUpdateRequest.Font( + * "Roboto-Regular", + * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), + * Collections.emptyList())))) + * .build(), fm.getFontConfig().getConfigVersion()); + * </pre> + * + * <p>You can update or add font files in the same request by calling + * {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}. + * The following example adds "YourFont" font file and defines "your-font" font family in the same + * request. In this case, the font file represented by {@code yourFontFd} should be an OpenType + * compliant font file and have "YourFont" as PostScript name (ID=6) in 'name' table. + * <pre> + * FontManager fm = getContext().getSystemService(FontManager.class); + * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder() + * .addFontFileUpdateRequest(new FontFileUpdateRequest(yourFontFd, signature)) + * .addFontFamily(new FontFamilyUpdateRequest.FontFamily("your-font", Arrays.asList( + * new FontFamilyUpdateRequest.Font( + * "YourFont", + * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), + * Collections.emptyList())))) + * .build(), fm.getFontConfig().getConfigVersion()); + * </pre> + * + * @hide + */ +@SystemApi +public final class FontFamilyUpdateRequest { + + /** + * A font family definition. + */ + public static final class FontFamily { + @NonNull + private final String mName; + @NonNull + private final List<Font> mFonts; + + /** + * Constructs a FontFamily. + * + * <p>A font family has a name to identify the font family. Apps can use + * {@link android.graphics.Typeface#create(String, int)} or XML resources to use a specific + * font family. + * + * <p>A font family consists of multiple fonts with different styles. The style information + * can be specified by {@link Font}. + * + * @see android.graphics.Typeface#create(String, int) + * @see Font + */ + public FontFamily(@NonNull String name, @NonNull List<Font> fonts) { + Objects.requireNonNull(name); + Preconditions.checkStringNotEmpty(name); + Objects.requireNonNull(fonts); + Preconditions.checkCollectionElementsNotNull(fonts, "fonts"); + Preconditions.checkCollectionNotEmpty(fonts, "fonts"); + mName = name; + mFonts = fonts; + } + + /** + * Returns the name of this family. + */ + @NonNull + public String getName() { + return mName; + } + + /** + * Returns the fonts in this family. + */ + @NonNull + public List<Font> getFonts() { + return mFonts; + } + } + + /** + * A single entry in a font family representing a font. + */ + public static final class Font { + + @NonNull + private final String mPostScriptName; + @NonNull + private final FontStyle mStyle; + @NonNull + private final List<FontVariationAxis> mAxes; + + /** + * Constructs a FontStyleVariation. + * + * <p>A font has a PostScript name to identify the font file to use, a {@link FontStyle} + * to specify the style, and a list of {@link FontVariationAxis} to specify axis tags and + * values for variable fonts. If the font file identified by {@code postScriptName} is not a + * variable font, {@code axes} must be empty. + * + * @param postScriptName The PostScript name of the font file to use. PostScript name is in + * Name ID 6 field in 'name' table, as specified by OpenType + * specification. + * @param style The style for this font. + * @param axes A list of {@link FontVariationAxis} to specify axis tags and values + * for variable fonts. + */ + public Font(@NonNull String postScriptName, @NonNull FontStyle style, + @NonNull List<FontVariationAxis> axes) { + Objects.requireNonNull(postScriptName); + Preconditions.checkStringNotEmpty(postScriptName); + Objects.requireNonNull(style); + Objects.requireNonNull(axes); + Preconditions.checkCollectionElementsNotNull(axes, "axes"); + mPostScriptName = postScriptName; + mStyle = style; + mAxes = axes; + } + + /** + * Returns PostScript name of the font file to use. + */ + @NonNull + public String getPostScriptName() { + return mPostScriptName; + } + + /** + * Returns the style. + */ + @NonNull + public FontStyle getStyle() { + return mStyle; + } + + /** + * Returns the list of {@link FontVariationAxis}. + */ + @NonNull + public List<FontVariationAxis> getAxes() { + return mAxes; + } + } + + /** + * Builds a {@link FontFamilyUpdateRequest}. + */ + public static final class Builder { + @NonNull + private final List<FontFileUpdateRequest> mFontFileUpdateRequests = new ArrayList<>(); + @NonNull + private final List<FontFamily> mFontFamilies = new ArrayList<>(); + + /** + * Constructs a FontFamilyUpdateRequest.Builder. + */ + public Builder() { + } + + /** + * Adds a {@link FontFileUpdateRequest} to execute as a part of the constructed + * {@link FontFamilyUpdateRequest}. + * + * @param request A font file update request. + * @return This builder object. + */ + @NonNull + public Builder addFontFileUpdateRequest(@NonNull FontFileUpdateRequest request) { + Objects.requireNonNull(request); + mFontFileUpdateRequests.add(request); + return this; + } + + /** + * Adds a font family to update an existing font family in the system font config or + * add as a new font family to the system font config. + * + * @param fontFamily An font family definition to add or update. + * @return This builder object. + */ + @NonNull + public Builder addFontFamily(@NonNull FontFamily fontFamily) { + Objects.requireNonNull(fontFamily); + mFontFamilies.add(fontFamily); + return this; + } + + /** + * Builds a {@link FontFamilyUpdateRequest}. + */ + @NonNull + public FontFamilyUpdateRequest build() { + return new FontFamilyUpdateRequest(mFontFileUpdateRequests, mFontFamilies); + } + } + + @NonNull + private final List<FontFileUpdateRequest> mFontFiles; + + @NonNull + private final List<FontFamily> mFontFamilies; + + private FontFamilyUpdateRequest(@NonNull List<FontFileUpdateRequest> fontFiles, + @NonNull List<FontFamily> fontFamilies) { + mFontFiles = fontFiles; + mFontFamilies = fontFamilies; + } + + /** + * Returns the list of {@link FontFileUpdateRequest} that will be executed as a part of this + * request. + */ + @NonNull + public List<FontFileUpdateRequest> getFontFileUpdateRequests() { + return mFontFiles; + } + + /** + * Returns the list of {@link FontFamily} that will be updated in this request. + */ + @NonNull + public List<FontFamily> getFontFamilies() { + return mFontFamilies; + } +} diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java new file mode 100644 index 000000000000..cf1dca965216 --- /dev/null +++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.fonts; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.ParcelFileDescriptor; + +import java.util.Objects; + +/** + * Request for updating a font file on the system. + * + * @hide + */ +@SystemApi +public final class FontFileUpdateRequest { + + private final ParcelFileDescriptor mParcelFileDescriptor; + private final byte[] mSignature; + + /** + * Creates a FontFileUpdateRequest with the given file and signature. + * + * @param parcelFileDescriptor A file descriptor of the font file. + * @param signature A PKCS#7 detached signature for verifying the font file. + */ + public FontFileUpdateRequest(@NonNull ParcelFileDescriptor parcelFileDescriptor, + @NonNull byte[] signature) { + Objects.requireNonNull(parcelFileDescriptor); + Objects.requireNonNull(signature); + mParcelFileDescriptor = parcelFileDescriptor; + mSignature = signature; + } + + /** + * Returns the file descriptor of the font file. + */ + @NonNull + public ParcelFileDescriptor getParcelFileDescriptor() { + return mParcelFileDescriptor; + } + + /** + * Returns the PKCS#7 detached signature for verifying the font file. + */ + @NonNull + public byte[] getSignature() { + return mSignature; + } +} diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index abb4f9fa7eef..e512cf1bbb1f 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -35,6 +35,8 @@ import com.android.internal.graphics.fonts.IFontManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -58,7 +60,7 @@ public class FontManager { RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH, RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME, RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG, - RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION }) + RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_FONT_NOT_FOUND }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} @@ -131,9 +133,10 @@ public class FontManager { public static final int RESULT_ERROR_VERSION_MISMATCH = -8; /** - * Indicates a failure due to IPC communication. + * Indicates a failure occurred because a font with the specified PostScript name could not be + * found. */ - public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; + public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; /** * Indicates a failure of opening font file. @@ -205,42 +208,40 @@ public class FontManager { } /** - * Update system installed font file. + * Update a system installed font file. * * <p> - * To protect devices, system font updater relies on the Linux Kernel feature called fs-verity. - * If the device is not ready for fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be + * To protect devices, system font updater relies on a Linux Kernel feature called fs-verity. + * If the device does not support fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be * returned. * - * Android only accepts OpenType compliant font files. If other font files are provided, + * <p>Android only accepts OpenType compliant font files. If other font files are provided, * {@link #RESULT_ERROR_INVALID_FONT_FILE} will be returned. * - * The font file to be updated is identified by PostScript name stored in name table. If the - * font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} will be - * returned. + * <p>The font file to be updated is identified by PostScript name stored in the name table. If + * the font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} + * will be returned. * - * The entire font file is verified with the given signature for the system installed - * certificate. If the system cannot verify the font contents, + * <p>The entire font file is verified with the given signature using system installed + * certificates. If the system cannot verify the font file contents, * {@link #RESULT_ERROR_VERIFICATION_FAILURE} will be returned. * - * The font file must have newer or equal revision number in the head table. In other words, the - * downgrading font file is not allowed. If the older font file is provided, + * <p>The font file must have a newer revision number in the head table. In other words, it is + * not allowed to downgrade a font file. If an older font file is provided, * {@link #RESULT_ERROR_DOWNGRADING} will be returned. * - * The caller must specify the base config version for keeping consist system configuration. If - * the system configuration is updated for some reason between you get config with - * {@link #getFontConfig()} and calling this method, {@link #RESULT_ERROR_VERSION_MISMATCH} will - * be returned. Get the latest font configuration by calling {@link #getFontConfig()} again and - * try with the latest config version again. - * - * @param pfd A file descriptor of the font file. - * @param signature A PKCS#7 detached signature for verifying entire font files. - * @param baseVersion A base config version to be updated. You can get latest config version by - * {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If the - * system has newer config version, the update will fail with - * {@link #RESULT_ERROR_VERSION_MISMATCH}. Try to get the latest config and - * try update again. - * @return result code. + * <p>The caller must specify the base config version for keeping the font configuration + * consistent. If the font configuration is updated for some reason between the time you get + * a configuration with {@link #getFontConfig()} and the time when you call this method, + * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by + * calling {@link #getFontConfig()} and call this method again with the latest config version. + * + * @param request A {@link FontFileUpdateRequest} to execute. + * @param baseVersion A base config version to be updated. You can get the latest config version + * by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If + * the system has a newer config version, the update will fail with + * {@link #RESULT_ERROR_VERSION_MISMATCH}. + * @return A result code. * * @see FontConfig#getConfigVersion() * @see #getFontConfig() @@ -253,18 +254,88 @@ public class FontManager { * @see #RESULT_ERROR_DOWNGRADING * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG * @see #RESULT_ERROR_FONT_UPDATER_DISABLED - * @see #RESULT_ERROR_REMOTE_EXCEPTION */ @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile( + @NonNull FontFileUpdateRequest request, @IntRange(from = 0) int baseVersion) { + try { + return mIFontManager.updateFontFile(new FontUpdateRequest( + request.getParcelFileDescriptor(), request.getSignature()), baseVersion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @deprecated Use {@link #updateFontFile(FontFileUpdateRequest, int)} + */ + // TODO: Remove this API before Developer Preview 3. + @Deprecated + @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile( @NonNull ParcelFileDescriptor pfd, @NonNull byte[] signature, @IntRange(from = 0) int baseVersion ) { + return updateFontFile(new FontFileUpdateRequest(pfd, signature), baseVersion); + } + + + /** + * Update or add system wide font families. + * + * <p>This method will update existing font families or add new font families. The updated + * font family definitions will be used when creating {@link android.graphics.Typeface} objects + * with using {@link android.graphics.Typeface#create(String, int)} specifying the family name, + * or through XML resources. Note that system fallback fonts cannot be modified by this method. + * Apps must use {@link android.graphics.Typeface.CustomFallbackBuilder} to use custom fallback + * fonts. + * + * <p>Font files can be updated by including {@link FontFileUpdateRequest} to {@code request} + * via {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}. + * The same constraints as {@link #updateFontFile} will apply when updating font files. + * + * <p>The caller must specify the base config version for keeping the font configuration + * consistent. If the font configuration is updated for some reason between the time you get + * a configuration with {@link #getFontConfig()} and the time when you call this method, + * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by + * calling {@link #getFontConfig()} and call this method again with the latest config version. + * + * @param request A {@link FontFamilyUpdateRequest} to execute. + * @param baseVersion A base config version to be updated. You can get the latest config version + * by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If + * the system has a newer config version, the update will fail with + * {@link #RESULT_ERROR_VERSION_MISMATCH}. + * @return A result code. + * @see FontConfig#getConfigVersion() + * @see #getFontConfig() + * @see #RESULT_SUCCESS + * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE + * @see #RESULT_ERROR_VERIFICATION_FAILURE + * @see #RESULT_ERROR_VERSION_MISMATCH + * @see #RESULT_ERROR_INVALID_FONT_FILE + * @see #RESULT_ERROR_INVALID_FONT_NAME + * @see #RESULT_ERROR_DOWNGRADING + * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG + * @see #RESULT_ERROR_FONT_UPDATER_DISABLED + * @see #RESULT_ERROR_FONT_NOT_FOUND + */ + @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFamily( + @NonNull FontFamilyUpdateRequest request, @IntRange(from = 0) int baseVersion) { + List<FontUpdateRequest> requests = new ArrayList<>(); + List<FontFileUpdateRequest> fontFileUpdateRequests = request.getFontFileUpdateRequests(); + for (int i = 0; i < fontFileUpdateRequests.size(); i++) { + FontFileUpdateRequest fontFile = fontFileUpdateRequests.get(i); + requests.add(new FontUpdateRequest(fontFile.getParcelFileDescriptor(), + fontFile.getSignature())); + } + List<FontFamilyUpdateRequest.FontFamily> fontFamilies = request.getFontFamilies(); + for (int i = 0; i < fontFamilies.size(); i++) { + FontFamilyUpdateRequest.FontFamily fontFamily = fontFamilies.get(i); + requests.add(new FontUpdateRequest(fontFamily.getName(), fontFamily.getFonts())); + } try { - return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature)); + return mIFontManager.updateFontFamily(requests, baseVersion); } catch (RemoteException e) { - Log.e(TAG, "Failed to call updateFont API", e); - return RESULT_ERROR_REMOTE_EXCEPTION; + throw e.rethrowFromSystemServer(); } } diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java index f551d6a175da..b79c8f62d492 100644 --- a/core/java/android/graphics/fonts/FontUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontUpdateRequest.java @@ -19,13 +19,17 @@ package android.graphics.fonts; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.LocaleList; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.text.FontConfig; +import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Represents a font update request. Currently only font install request is supported. @@ -80,6 +84,26 @@ public final class FontUpdateRequest implements Parcelable { mFontFamily = fontFamily; } + public FontUpdateRequest(@NonNull String postScriptName, + @NonNull List<FontFamilyUpdateRequest.Font> variations) { + // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily. + this(createFontFamily(postScriptName, variations)); + } + + private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName, + @NonNull List<FontFamilyUpdateRequest.Font> fonts) { + List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size()); + for (FontFamilyUpdateRequest.Font font : fonts) { + // TODO: Support .otf. + configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null, + font.getStyle(), 0 /* index */, + FontVariationAxis.toFontVariationSettings(font.getAxes()), + null /* fontFamilyName */)); + } + return new FontConfig.FontFamily(configFonts, postScriptName, + LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); + } + protected FontUpdateRequest(Parcel in) { mType = in.readInt(); mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 8bb0b184ce02..10a814acd70b 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -1917,9 +1917,18 @@ public final class StreamConfigurationMap { (3 << HAL_DATASPACE_TRANSFER_SHIFT) | (1 << HAL_DATASPACE_RANGE_SHIFT); - private static final int HAL_DATASPACE_DEPTH = 0x1000; - private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002; - private static final int HAL_DATASPACE_HEIF = 0x1003; + /** + * @hide + */ + public static final int HAL_DATASPACE_DEPTH = 0x1000; + /** + * @hide + */ + public static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002; + /** + * @hide + */ + public static final int HAL_DATASPACE_HEIF = 0x1003; private static final long DURATION_20FPS_NS = 50000000L; /** * @see #getDurations(int, int) diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 43480ab9cc44..49beeb3a2e96 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -127,6 +127,20 @@ public class ContextHubClient implements Closeable { * 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 b31b85fdabf6..7e484dda283c 100644 --- a/core/java/android/hardware/location/ContextHubClientCallback.java +++ b/core/java/android/hardware/location/ContextHubClientCallback.java @@ -117,10 +117,11 @@ public class ContextHubClientCallback { * 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 d11e0a9b6081..86f77c0bf138 100644 --- a/core/java/android/hardware/location/ContextHubTransaction.java +++ b/core/java/android/hardware/location/ContextHubTransaction.java @@ -81,7 +81,8 @@ public class ContextHubTransaction<T> { 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 @@ public class ContextHubTransaction<T> { * 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 383321bc3d69..bd40409f71c6 100644 --- a/core/java/android/hardware/location/OWNERS +++ b/core/java/android/hardware/location/OWNERS @@ -4,3 +4,6 @@ mstogaitis@google.com 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 ca79901c2bbe..7f07af79ab69 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -135,6 +135,12 @@ interface IUsbManager /* Resets the USB gadget. */ void resetUsbGadget(); + /* Set USB data on or off */ + boolean enableUsbDataSignal(boolean enable); + + /* Gets the USB Hal Version. */ + int getUsbHalVersion(); + /* Get the functionfs control handle for the given function. Usb * descriptors will already be written, and the handle will be * ready to use. diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 5bac481fb23e..841fd2f49967 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -516,6 +516,46 @@ public class UsbManager { 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 class UsbManager { }) 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 @@ public class UsbManager { } /** + * 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 @@ public class UsbManager { } /** + * 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/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl new file mode 100644 index 000000000000..271efe41a9ef --- /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/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 3e6237d99011..6353a25e745f 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; 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 @@ import java.util.concurrent.ConcurrentHashMap; * * @hide */ +@TestApi @SystemService(Context.NETWORK_POLICY_SERVICE) public class NetworkPolicyManager { @@ -125,6 +127,7 @@ public class NetworkPolicyManager { 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 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRestrictBackground(boolean restrictBackground) { try { @@ -361,6 +365,7 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean getRestrictBackground() { try { @@ -506,6 +511,8 @@ public class NetworkPolicyManager { /** * Get multipath preference for the given network. + * + * @hide */ public int getMultipathPreference(Network network) { try { @@ -624,7 +631,9 @@ public class NetworkPolicyManager { } /** @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/UidRange.java b/core/java/android/net/UidRange.java index 3bc0f9ca4e6a..b172ccc4e370 100644 --- a/core/java/android/net/UidRange.java +++ b/core/java/android/net/UidRange.java @@ -21,6 +21,7 @@ import static android.os.UserHandle.PER_USER_RANGE; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.util.Collection; @@ -45,6 +46,14 @@ public final class UidRange implements Parcelable { return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } + /** Creates a UidRange for the specified user. */ + public static UidRange createForUser(UserHandle user) { + final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1); + final int start = UserHandle.getUid(user, 0 /* appId */); + final int end = UserHandle.getUid(nextUser, 0) - 1; + return new UidRange(start, end); + } + /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { return start / PER_USER_RANGE; diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 1e30283a9e6c..f472ed4381d1 100644 --- a/packages/Connectivity/framework/src/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; 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.io.IOException; 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 @@ public class VpnManager { @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 @@ public class VpnManager { * * @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 @@ public class VpnManager { } /** + * 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 @@ public class VpnManager { } /** + * 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 index 8e90a119fe21..e43b0b6fa635 100644 --- a/packages/Connectivity/framework/src/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "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 @@ public class VpnService extends Service { @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/Debug.java b/core/java/android/os/Debug.java index eac03dc5d07e..e3b13f4f9f17 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -2566,6 +2566,14 @@ public final class Debug public static native long getDmabufTotalExportedKb(); /** + * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF + * heaps frameworks or -1 in the case of an error. + * + * @hide + */ + public static native long getDmabufHeapTotalExportedKb(); + + /** * Return memory size in kilobytes allocated for ION heaps or -1 if * /sys/kernel/ion/total_heaps_kb could not be read. * diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl index 52f0ce1f054f..4d160da22ff8 100644 --- a/core/java/android/os/ISystemConfig.aidl +++ b/core/java/android/os/ISystemConfig.aidl @@ -35,4 +35,9 @@ interface ISystemConfig { * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries */ Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); + + /** + * @see SystemConfigManager#getSystemPermissionUids + */ + int[] getSystemPermissionUids(String permissionName); } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index dac1edea7d3e..a04047df4af1 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -1,4 +1,6 @@ # 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 @@ -6,9 +8,11 @@ per-file IExternalVibratorService.aidl = michaelwr@google.com per-file IVibratorManagerService.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/PowerManager.java b/core/java/android/os/PowerManager.java index 059e932a9da9..3774fb595680 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1898,7 +1898,8 @@ public final class PowerManager { * 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 @@ public final class PowerManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + @RequiresPermission(anyOf = { + android.Manifest.permission.BATTERY_PREDICTION, + android.Manifest.permission.DEVICE_POWER + }) public void setBatteryDischargePrediction(@NonNull Duration timeRemaining, boolean isPersonalized) { if (timeRemaining == null) { diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java index 3f0632be90d1..9bfa8adc8571 100644 --- a/core/java/android/os/SystemConfigManager.java +++ b/core/java/android/os/SystemConfigManager.java @@ -111,4 +111,22 @@ public class SystemConfigManager { return Collections.emptyMap(); } } + + /** + * Get uids which have been granted given permission in system configuration. + * + * The uids and assigning permissions are defined on data/etc/platform.xml + * + * @param permissionName The target permission. + * @return The uids have been granted given permission in system configuration. + */ + @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) + @NonNull + public int[] getSystemPermissionUids(@NonNull String permissionName) { + try { + return mInterface.getSystemPermissionUids(permissionName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ef81ed768e56..f02e53249ce4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10780,6 +10780,13 @@ public final class Settings { "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/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index a750b689ee02..87add57f383d 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -95,6 +95,21 @@ public abstract class ExternalStorageService extends Service { 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 @@ public abstract class ExternalStorageService extends Service { 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 @@ public abstract class ExternalStorageService extends Service { }); } + @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 @@ public abstract class ExternalStorageService extends Service { } 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 d06671b3fb9f..2e0bd86c3d7d 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -32,4 +32,5 @@ oneway interface IExternalStorageService 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 000000000000..9d2e5b9441a3 --- /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 f334d9d3e874..f81ed3410d58 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 @@ interface IWallpaperConnection { 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 90392e65794a..fbb449d3875e 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 @@ oneway interface IWallpaperEngine { 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 82e0b4a1aecc..0b447d5bcf8a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y; 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.Matrix; 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.Looper; 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.InputEventReceiver; 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 com.android.internal.view.BaseSurfaceHolder; 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 abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { 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 abstract class WallpaperService extends Service { */ 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 @@ public abstract class WallpaperService extends Service { */ @VisibleForTesting public Engine(Supplier<Long> clockFunction, Handler handler) { - mClockFunction = clockFunction; - mHandler = handler; + mClockFunction = clockFunction; + mHandler = handler; } /** @@ -448,6 +472,19 @@ public abstract class WallpaperService extends Service { } /** + * 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { } /** + * 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 @@ public abstract class WallpaperService extends Service { mIsCreating = false; mSurfaceCreated = true; if (redrawNeeded) { + resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */); + processLocalColors(mPendingXOffset, mPendingXOffsetStep); } reposition(); mIWallpaperEngine.reportShown(); @@ -1209,6 +1272,7 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); + if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } } @@ -1278,6 +1342,405 @@ public abstract class WallpaperService extends Service { } 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 @@ public abstract class WallpaperService extends Service { }; } + 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 @@ public abstract class WallpaperService extends Service { 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 @@ public abstract class WallpaperService extends Service { } 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/View.java b/core/java/android/view/View.java index 1273b491d0e1..ba78f966b218 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22164,6 +22164,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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 6b13a290b20e..f1f6786aa43e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1375,6 +1375,7 @@ public final class ViewRootImpl implements ViewParent, final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets; mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent, attrs.getTitle().toString()); + mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); updateColorModeIfNeeded(attrs.getColorMode()); updateForceDarkMode(); if (mAttachInfo.mThreadedRenderer != null) { @@ -1941,6 +1942,10 @@ public final class ViewRootImpl implements ViewParent, mBlastBufferQueue.destroy(); mBlastBufferQueue = null; } + + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.setSurfaceControl(null); + } } /** @@ -2836,8 +2841,7 @@ public final class ViewRootImpl implements ViewParent, mScroller.abortAnimation(); } // Our surface is gone - if (mAttachInfo.mThreadedRenderer != null && - mAttachInfo.mThreadedRenderer.isEnabled()) { + if (isHardwareEnabled()) { mAttachInfo.mThreadedRenderer.destroy(); } } else if ((surfaceReplaced @@ -3923,8 +3927,15 @@ public final class ViewRootImpl implements ViewParent, }; } + /** + * @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; } @@ -4268,7 +4279,7 @@ public final class ViewRootImpl implements ViewParent, 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; @@ -7645,6 +7656,9 @@ public final class ViewRootImpl implements ViewParent, 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 af18293398da..4ecdd78f5a42 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1703,6 +1703,30 @@ public abstract class Window { 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/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 681fd147a51f..1b62266c12e2 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -25,8 +25,12 @@ 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; @@ -149,6 +153,8 @@ public class EdgeEffect { private float mDisplacement = 0.5f; private float mTargetDisplacement = 0.5f; private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW; + private Matrix mTmpMatrix = null; + private float[] mTmpPoints = null; /** * Construct a new EdgeEffect with a theme appropriate for the provided context. @@ -250,11 +256,18 @@ public class EdgeEffect { public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); mTargetDisplacement = displacement; - if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { + if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration + && mEdgeEffectType == TYPE_GLOW) { return; } if (mState != STATE_PULL) { - mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + if (mEdgeEffectType == TYPE_STRETCH) { + // Restore the mPullDistance to the fraction it is currently showing -- we want + // to "catch" the current stretch value. + mPullDistance = mDistance; + } else { + mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + } } mState = STATE_PULL; @@ -313,6 +326,15 @@ public class EdgeEffect { public float onPullDistance(float deltaDistance, float displacement) { float finalDistance = Math.max(0f, deltaDistance + mDistance); float delta = finalDistance - mDistance; + if (delta == 0f && mDistance == 0f) { + return 0f; // No pull, don't do anything. + } + + if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) { + // Catch the edge glow in the middle of an animation. + mPullDistance = mDistance; + mState = STATE_PULL; + } onPull(delta, displacement); return delta; } @@ -466,33 +488,59 @@ public class EdgeEffect { * 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(); - - final int count = canvas.save(); - - final float centerX = mBounds.centerX(); - final float centerY = mBounds.height() - mRadius; - - 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; - - canvas.clipRect(mBounds); - canvas.translate(translateX, 0); - mPaint.setAlpha((int) (0xff * mGlowAlpha)); - canvas.drawCircle(centerX, centerY, mRadius, mPaint); - canvas.restoreToCount(count); + if (mEdgeEffectType == TYPE_GLOW) { + update(); + final int count = canvas.save(); + + final float centerX = mBounds.centerX(); + final float centerY = mBounds.height() - mRadius; + + 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; + + 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]; + + RenderNode renderNode = recordingCanvas.mNode; + + // TODO: use stretchy RenderEffect and use internal API when it is ready + // TODO: wrap existing RenderEffect + renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y)); + } boolean oneLastFrame = false; - if (mState == STATE_RECEDE && mGlowScaleY == 0) { + if (mState == STATE_RECEDE && mDistance == 0) { mState = STATE_IDLE; oneLastFrame = true; } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 97dbb1542717..6dedd12a2730 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -89,7 +89,7 @@ public class HorizontalScrollView extends FrameLayout { */ @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 @@ public class HorizontalScrollView extends FrameLayout { * 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 class HorizontalScrollView extends FrameLayout { 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 @@ public class HorizontalScrollView extends FrameLayout { * 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 @@ public class HorizontalScrollView extends FrameLayout { 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 @@ public class HorizontalScrollView extends FrameLayout { 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 @@ public class HorizontalScrollView extends FrameLayout { 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 6cb4b81827b9..4e3d99b43cf6 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -48,6 +48,7 @@ import android.content.res.Resources; 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.invoke.MethodType; 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 @@ public class RemoteViews implements Parcelable, Filter { */ 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 @@ public class RemoteViews implements Parcelable, Filter { * 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 @@ public class RemoteViews implements Parcelable, Filter { */ 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { } /** + * 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { */ 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 @@ public class RemoteViews implements Parcelable, Filter { * @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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { /** @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 @@ public class RemoteViews implements Parcelable, Filter { } /** @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 @@ public class RemoteViews implements Parcelable, Filter { /** @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 @@ public class RemoteViews implements Parcelable, Filter { /** @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 @@ public class RemoteViews implements Parcelable, Filter { /** @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 class RemoteViews implements Parcelable, Filter { } 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { * 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 @@ public class RemoteViews implements Parcelable, Filter { * 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 f3de9828c4aa..64d09de49f2d 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -98,7 +98,7 @@ public class ScrollView extends FrameLayout { */ @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 @@ public class ScrollView extends FrameLayout { */ @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 class ScrollView extends FrameLayout { 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 @@ public class ScrollView extends FrameLayout { * 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 @@ public class ScrollView extends FrameLayout { 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 @@ public class ScrollView extends FrameLayout { 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 @@ public class ScrollView extends FrameLayout { 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/TextView.java b/core/java/android/widget/TextView.java index fe37c5350511..0f2089a5463f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12940,17 +12940,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 88b2257a55b1..8f541d0bd194 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -42,6 +42,11 @@ oneway interface ITaskOrganizer { 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 000000000000..4b88a9bc39de --- /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/window/SplashScreenView.aidl b/core/java/android/window/SplashScreenView.aidl new file mode 100644 index 000000000000..cc7ac1edce80 --- /dev/null +++ b/core/java/android/window/SplashScreenView.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +/** @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 000000000000..35ccfca101d3 --- /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 2282cc567936..63b9e9befb77 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -95,6 +95,12 @@ public final class StartingWindowInfo implements Parcelable { */ public int startingWindowTypeParameter; + /** + * Specifies a theme for the splash screen. + * @hide + */ + public int splashScreenThemeResId; + public StartingWindowInfo() { } @@ -115,6 +121,7 @@ public final class StartingWindowInfo implements Parcelable { dest.writeTypedObject(topOpaqueWindowInsetsState, flags); dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); dest.writeTypedObject(mainWindowLayoutParams, flags); + dest.writeInt(splashScreenThemeResId); } void readFromParcel(@NonNull Parcel source) { @@ -124,6 +131,7 @@ public final class StartingWindowInfo implements Parcelable { topOpaqueWindowLayoutParams = source.readTypedObject( WindowManager.LayoutParams.CREATOR); mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); + splashScreenThemeResId = source.readInt(); } @Override @@ -135,7 +143,8 @@ public final class StartingWindowInfo implements Parcelable { + 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 cdb4762a4f0a..217ade82b336 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -105,6 +105,12 @@ public class TaskOrganizer extends WindowOrganizer { 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 @@ public class TaskOrganizer extends WindowOrganizer { } @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/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl new file mode 100644 index 000000000000..5d02a29edcd5 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.compat; + +parcelable CompatibilityOverrideConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java new file mode 100644 index 000000000000..1c222a73eabc --- /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 7aca36af919d..249c13402e4f 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -21,6 +21,7 @@ import com.android.internal.compat.IOverrideValidator; import java.util.Map; parcelable CompatibilityChangeConfig; +parcelable CompatibilityOverrideConfig; parcelable CompatibilityChangeInfo; /** * Platform private API for talking with the PlatformCompat service. @@ -152,6 +153,17 @@ interface IPlatformCompat { /** * Adds overrides to compatibility changes. * + * <p>Kills the app to allow the changes to take effect. + * + * @param overrides parcelable containing the compat change overrides to be applied + * @param packageName the package name of the app whose changes will be overridden + * @throws SecurityException if overriding changes is not permitted + */ + void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName); + + /** + * Adds overrides to compatibility changes. + * * <p>Does not kill the app, to be only used in tests. * * @param overrides parcelable containing the compat change overrides to be applied diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl index dfcc91421b9e..1c7eca8b6c8e 100644 --- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl +++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl @@ -20,6 +20,8 @@ import android.os.ParcelFileDescriptor; import android.graphics.fonts.FontUpdateRequest; import android.text.FontConfig; +import java.util.List; + /** * System private interface for talking with * {@link com.android.server.graphics.fonts.FontManagerService}. @@ -28,5 +30,7 @@ import android.text.FontConfig; interface IFontManager { FontConfig getFontConfig(); - int updateFont(int baseVersion, in FontUpdateRequest request); + int updateFontFile(in FontUpdateRequest request, int baseVersion); + + int updateFontFamily(in List<FontUpdateRequest> request, int baseVersion); } diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java new file mode 100644 index 000000000000..28525478be05 --- /dev/null +++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +/** CPU tracking using eBPF. */ +public final class KernelCpuBpfTracking { + private KernelCpuBpfTracking() { + } + + /** Returns whether CPU tracking using eBPF is supported. */ + public static native boolean isSupported(); +} diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java index 50331e3338dc..06760e140de1 100644 --- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java +++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java @@ -23,9 +23,6 @@ public final class KernelCpuTotalBpfMapReader { 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/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index adebde016bb3..9840013935f8 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.FrameLayout; 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind cb.onAttachedToWindow(); } + getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener); + updateBackgroundDrawable(); + if (mFeatureId == -1) { /* * The main window has been attached, try to restore any panels @@ -1755,6 +1794,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 5df175e8aee5..d06413c3ca15 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -258,6 +258,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Drawable mBackgroundDrawable = null; Drawable mBackgroundFallbackDrawable = null; + int mBackgroundBlurRadius = 0; + private boolean mLoadElevation = true; private float mElevation; @@ -1523,6 +1525,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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/jni/Android.bp b/core/jni/Android.bp index 9510f2700fe2..6983d35c5a3f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -184,6 +184,7 @@ cc_library_shared { "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", + "com_android_internal_os_KernelCpuBpfTracking.cpp", "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp", "com_android_internal_os_KernelCpuUidBpfMapReader.cpp", "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 8879111bc2f5..38bcc0f4c59e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -190,6 +190,7 @@ extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 a7950f217243..8dcb2100b5fb 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -829,6 +829,16 @@ static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject claz return dmabufTotalSizeKb; } +static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) { + jlong dmabufHeapTotalSizeKb = -1; + uint64_t size; + + if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) { + dmabufHeapTotalSizeKb = size; + } + return dmabufHeapTotalSizeKb; +} + static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) { jlong poolsSizeKb = -1; uint64_t size; @@ -986,6 +996,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_os_Debug_getDmabufTotalExportedKb }, { "getGpuDmaBufUsageKb", "()J", (void*)android_os_Debug_getGpuDmaBufUsageKb }, + { "getDmabufHeapTotalExportedKb", "()J", + (void*)android_os_Debug_getDmabufHeapTotalExportedKb }, { "getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb }, { "getDmabufMappedSizeKb", "()J", diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp new file mode 100644 index 000000000000..e6a82f6d0cb5 --- /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 d8446ca2881d..72492381e31a 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 jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject ca 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/vibratorservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 9e42e9edfd27..aab054f4bf73 100644 --- a/core/proto/android/server/vibratorservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -15,7 +15,7 @@ */ syntax = "proto2"; -package com.android.server; +package com.android.server.vibrator; option java_multiple_files = true; @@ -51,12 +51,25 @@ message ComposedProto { // 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 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; @@ -68,30 +81,31 @@ message VibrationAttributesProto { 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 int64 end_time = 2; + optional CombinedVibrationEffectProto effect = 3; + optional CombinedVibrationEffectProto original_effect = 4; optional VibrationAttributesProto attributes = 5; optional int32 status = 6; } -// Next id: 17 -message VibratorServiceDumpProto { +// Next id: 18 +message VibratorManagerServiceDumpProto { 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; + 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 101bb8152dbb..6456e4e2a59a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4381,6 +4381,12 @@ <permission android:name="android.permission.POWER_SAVER" android:protectionLevel="signature|privileged" /> + <!-- Allows providing the system with battery predictions. + Superseded by DEVICE_POWER permission. @hide @SystemApi + --> + <permission android:name="android.permission.BATTERY_PREDICTION" + android:protectionLevel="signature|privileged" /> + <!-- Allows access to the PowerManager.userActivity function. <p>Not for use by third-party applications. @hide @SystemApi --> <permission android:name="android.permission.USER_ACTIVITY" @@ -5480,6 +5486,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 08e1060ebad3..1a0912ee47dc 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 7ba21e840a59..5274ef2f6dce 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 ed2b5aacb236..6a9e62110ba4 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 8b19e4ae3561..7cee3820172b 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 000000000000..513da5e431e5 --- /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 aeb4fc468fe4..67b810e4ebdf 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/colors.xml b/core/res/res/values/colors.xml index 5546621b6ee8..e7c4947bed5f 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -245,19 +245,19 @@ <color name="system_main_0">#ffffff</color> <!-- Shade of the main system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_50">#f0f0f0</color> + <color name="system_main_50">#f2f2f2</color> <!-- Shade of the main system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_100">#e2e2e2</color> + <color name="system_main_100">#e3e3e3</color> <!-- Shade of the main system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_200">#c6c6c6</color> + <color name="system_main_200">#c7c7c7</color> <!-- Shade of the main system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_300">#ababab</color> <!-- Shade of the main system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_400">#909090</color> + <color name="system_main_400">#8f8f8f</color> <!-- Shade of the main system color at 50% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_500">#757575</color> @@ -266,13 +266,13 @@ <color name="system_main_600">#5e5e5e</color> <!-- Shade of the main system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_700">#464646</color> + <color name="system_main_700">#474747</color> <!-- Shade of the main system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_800">#303030</color> <!-- Shade of the main system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_900">#1b1b1b</color> + <color name="system_main_900">#1f1f1f</color> <!-- Darkest shade of the main color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_main_1000">#000000</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b7c755ef7f6b..dd048f3e0993 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1946,6 +1946,8 @@ <string name="config_systemAutomotiveProjection" translatable="false"></string> <!-- The name of the package that will hold the system cluster service role. --> <string name="config_systemAutomotiveCluster" translatable="false"></string> + <!-- The name of the package that will hold the system shell role. --> + <string name="config_systemShell" translatable="false">com.android.shell</string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index debdab0b37ba..706641985e20 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 068987ec8afe..fdef08bc5faa 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3065,6 +3065,13 @@ <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"> @@ -3105,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"> @@ -3120,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 4109d4c9f6f9..f66d33b02b69 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 049ba23a1a97..87ae1623ca92 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -167,6 +167,9 @@ please see themes_device_defaults.xml. <!-- 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 ce4ee87e52a0..e7e049da18c9 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -224,6 +224,9 @@ easier. <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/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS index 1a28b73de8cd..9a9b4748d4f1 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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 0484a9ab582e..fe4d0cf80ee0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -316,6 +316,8 @@ applications that come with the platform <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 @@ applications that come with the platform <!-- 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 69c30f3ca05c..84da930aac54 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -889,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", @@ -1081,12 +1075,6 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, - "-856025122": { - "message": "SURFACE transparentRegionHint=%s: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-855366859": { "message": " merging children in from %s: %s", "level": "VERBOSE", @@ -1675,6 +1663,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", @@ -2821,12 +2815,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1217926207": { - "message": "Activity not running, resuming next.", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/Task.java" - }, "1219600119": { "message": "addWindow: win=%s Callers=%s", "level": "DEBUG", @@ -3331,6 +3319,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "1847414670": { + "message": "Activity not running or entered PiP, resuming next.", + "level": "VERBOSE", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/Task.java" + }, "1853793312": { "message": "Notify removed startingWindow %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index b70fa0e693c2..88cf96a9eb4e 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -39,6 +39,7 @@ import android.view.IGraphicsStatsCallback; 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 @@ public class HardwareRenderer { } /** + * 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 @@ public class HardwareRenderer { private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer); + private static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl); + private static native boolean nPause(long nativeProxy); private static native void nSetStopped(long nativeProxy, boolean stopped); diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java new file mode 100644 index 000000000000..80f65f919fa6 --- /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 bab80cee2b26..5024875aab3c 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.content.res.TypedArray; 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.XmlPullParser; 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 @@ import java.util.Arrays; * </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 class RippleDrawable extends LayerDrawable { */ 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 @@ public class RippleDrawable extends LayerDrawable { /** 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 @@ public class RippleDrawable extends LayerDrawable { */ 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { mRipple.onBoundsChange(); } + mState.mMaxRadius = mState.mMaxRadius <= 0 && mState.mRippleStyle != STYLE_SOLID + ? (int) computeRadius() + : mState.mMaxRadius; invalidateSelf(); } @@ -330,7 +407,11 @@ public class RippleDrawable extends LayerDrawable { // 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { @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 @@ public class RippleDrawable extends LayerDrawable { */ @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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { // 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { 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 @@ public class RippleDrawable extends LayerDrawable { @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 @@ public class RippleDrawable extends LayerDrawable { 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 000000000000..500efdd84854 --- /dev/null +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.graphics.Color; +import android.graphics.RuntimeShader; +import android.graphics.Shader; + +final class RippleShader extends RuntimeShader { + private static final String SHADER = "uniform float2 in_origin;\n" + + "uniform float in_maxRadius;\n" + + "uniform float in_progress;\n" + + "uniform float in_hasMask;\n" + + "uniform float4 in_color;\n" + + "uniform shader in_shader;\n" + + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + " + + "(pf.y - p0.y) * (pf.y - p0.y)); }\n" + + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n" + + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * " + + "43758.5453123); }\n" + + "float4 main(float2 p)\n" + + "{\n" + + " float fraction = in_progress;\n" + + " float2 fragCoord = p;//sk_FragCoord.xy;\n" + + " float maxDist = in_maxRadius;\n" + + " float fragDist = dist2(in_origin, fragCoord.xy);\n" + + " float circleRadius = maxDist * fraction;\n" + + " float colorVal = (fragDist - circleRadius) / maxDist;\n" + + " float d = fragDist < circleRadius \n" + + " ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n" + + " : 1. - abs(colorVal * 5.);\n" + + " d = smoothstep(0., 1., d);\n" + + " float divider = 2.;\n" + + " float x = floor(fragCoord.x / divider);\n" + + " float y = floor(fragCoord.y / divider);\n" + + " float density = .95;\n" + + " d = rand(float2(x, y)) > density ? d : d * .2;\n" + + " d = d * rand(float2(fraction, x * y));\n" + + " float alpha = 1. - pow(fraction, 2.);\n" + + " if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n" + + " return in_color * d * alpha;\n" + + "}\n"; + + RippleShader() { + super(SHADER, false); + } + + public void setShader(@NonNull Shader s) { + setInputShader("in_shader", s); + } + + public void setRadius(float radius) { + setUniform("in_maxRadius", radius); + } + + public void setOrigin(float x, float y) { + setUniform("in_origin", new float[] {x, y}); + } + + public void setProgress(float progress) { + setUniform("in_progress", progress); + } + + public void setHasMask(boolean hasMask) { + setUniform("in_hasMask", hasMask ? 1 : 0); + } + + public void setColor(@ColorInt int colorIn) { + Color color = Color.valueOf(colorIn); + this.setUniform("in_color", new float[] {color.red(), + color.green(), color.blue(), color.alpha()}); + } +} diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java index 7bd581723d18..d1fe2cdbcd77 100644 --- a/graphics/java/android/graphics/fonts/FontVariationAxis.java +++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java @@ -23,6 +23,7 @@ import android.os.Build; import android.text.TextUtils; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.regex.Pattern; @@ -189,6 +190,17 @@ public final class FontVariationAxis { return TextUtils.join(",", axes); } + /** + * Stringify the array of FontVariationAxis. + * @hide + */ + public static @NonNull String toFontVariationSettings(@Nullable List<FontVariationAxis> axes) { + if (axes == null) { + return ""; + } + return TextUtils.join(",", axes); + } + @Override public boolean equals(@Nullable Object o) { if (o == this) { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index e92eaca2b6e9..2b0d7e53b749 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -236,6 +236,47 @@ import javax.security.auth.x500.X500Principal; * keyStore.load(null); * key = (SecretKey) keyStore.getKey("key2", null); * }</pre> + * + * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3> + * This example illustrates how to generate an elliptic curve key pair, used to establish a shared + * secret with another party using ECDH key agreement. + * <pre> {@code + * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + * KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + * keyPairGenerator.initialize( + * new KeyGenParameterSpec.Builder( + * "eckeypair", + * KeyProperties.PURPOSE_AGREE_KEY) + * .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) + * .build()); + * KeyPair myKeyPair = keyPairGenerator.generateKeyPair(); + * + * // Exchange public keys with server. A new ephemeral key MUST be used for every message. + * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server. + * + * // Create a shared secret based on our private key and the other party's public key. + * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore"); + * keyAgreement.init(myKeyPair.getPrivate()); + * keyAgreement.doPhase(serverEphemeralPublicKey, true); + * byte[] sharedSecret = keyAgreement.generateSecret(); + * + * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation + * // function with some other data: "salt" and "info". Salt is an optional random value, + * // omitted in this example. It's good practice to include both public keys and any other + * // key negotiation data in info. Here we use the public keys and a label that indicates + * // messages encrypted with this key are coming from the server. + * byte[] salt = {}; + * ByteArrayOutputStream info = new ByteArrayOutputStream(); + * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8)); + * info.write(myKeyPair.getPublic().getEncoded()); + * info.write(serverEphemeralPublicKey.getEncoded()); + * + * // This example uses the Tink library and the HKDF key derivation function. + * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf( + * "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32)); + * byte[] associatedData = {}; + * return key.decrypt(ciphertext, associatedData); + * } */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 3ebca6ad302d..293ab05e0b10 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -100,6 +100,15 @@ public abstract class KeyProperties { /** * Purpose of key: creating a shared ECDH secret through key agreement. + * + * <p>A key having this purpose can be combined with the elliptic curve public key of another + * party to establish a shared secret over an insecure channel. It should be used as a + * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is + * available <a + * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh" + * >here</a>). + * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this + * article</a> for a more detailed explanation. */ public static final int PURPOSE_AGREE_KEY = 1 << 6; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java index 0bf6965beb5f..5c91cf41bfc6 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 @@ class SampleExtensionImpl extends StubExtension implements 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 1094a0e2b4da..d3700f88d97f 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 @@ class SampleSidecarImpl extends StubSidecar implements 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 96e0559b0df6..0540aee1d6d9 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -71,27 +71,6 @@ genrule { "$(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 @@ android_library { 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 000000000000..cd3153145be3 --- /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 9c3d84e72f8c..000000000000 --- 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 13f1fddfdfb6..24198659e15d 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 034e65c608a3..583964b2f4a4 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 afe523af7cb0..6984ea458ccf 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 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (!mLeashByTaskId.contains(taskId)) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mLeashByTaskId.get(taskId)); + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; pw.println(prefix + this); pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b22f358c0781..efc55c4fbe31 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 android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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,9 +40,6 @@ import android.window.StartingWindowInfo; 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; @@ -83,6 +82,16 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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) {}; } @@ -111,23 +120,23 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + new StartingSurfaceDrawer(context, mainExecutor)); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override + public void copySplashScreenView(int taskId) { + mStartingSurfaceDrawer.copySplashScreenView(taskId); + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); @@ -354,8 +368,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { 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 @@ public class ShellTaskOrganizer extends TaskOrganizer { } 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 bb8a97344664..5992447bd6da 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 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (mTaskInfo.taskId != taskId) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mTaskLeash); + } + + @Override public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index bab5140e2f52..79f9dcd8a1fb 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 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (getRootTaskId() == taskId) { + b.setParent(mRootTaskLeash); + } else if (getTaskId1() == taskId) { + b.setParent(mTaskLeash1); + } else if (getTaskId2() == taskId) { + b.setParent(mTaskLeash2); + } else { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java index 5a493c234ce3..05526018d73f 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 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (!mLeashByTaskId.contains(taskId)) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mLeashByTaskId.get(taskId)); + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 3064af6f5170..71331dfcef44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -66,7 +66,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.transition.Transitions; @@ -585,6 +584,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } @Override + public boolean supportSizeCompatUI() { + // PIP doesn't support size compat. + return false; + } + + @Override public void onFixedRotationStarted(int displayId, int newRotation) { mNextRotation = newRotation; mWaitForFixedRotation = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index ae5300502993..725f87d93e4e 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 @@ public class PhonePipMenuController implements PipMenuController { } /** - * 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 @@ public class PhonePipMenuController implements PipMenuController { } /** - * 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 962c4672644a..1cf3a48e9575 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 @@ public class PipMenuView extends FrameLayout { 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 @@ public class PipMenuView extends FrameLayout { 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 @@ public class PipMenuView extends FrameLayout { 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 @@ public class PipMenuView extends FrameLayout { 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 b19dcae2def8..eae8945ce6be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -17,8 +17,7 @@ package com.android.wm.shell.pip.phone; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; -import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -325,7 +324,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, + " callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); - mMenuController.hideMenuWithoutResize(); + mMenuController.hideMenu(false /* animate */, false /* resize */); mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); } @@ -338,7 +337,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " ")); } cancelPhysicsAnimation(); - mMenuController.hideMenuWithoutResize(); + mMenuController.hideMenu(true /* animate*/, false /* resize */); mPipTaskOrganizer.removePip(); } @@ -371,9 +370,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion. */ - void stashToEdge(float velocityX, @Nullable Runnable postBoundsUpdateCallback) { - mPipBoundsState.setStashed(velocityX < 0 ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT); - movetoTarget(velocityX, 0 /* velocityY */, postBoundsUpdateCallback, true /* isStash */); + void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) { + velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY; + movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */); } private void movetoTarget( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 1ef9ffa494f4..78ee1868eee7 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 @@ public class PipResizeGestureHandler { // 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 @@ public class PipResizeGestureHandler { 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 class PipResizeGestureHandler { 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 @@ public class PipResizeGestureHandler { } if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { - mPhonePipMenuController.hideMenuWithoutResize(); - mPhonePipMenuController.hideMenu(); + mPhonePipMenuController.hideMenu(false /* animate */, + false /* resize */); } final Rect currentPipBounds = mPipBoundsState.getBounds(); mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index e69c6f2f47bc..afc7b5294a2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -19,7 +19,9 @@ package com.android.wm.shell.pip.phone; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT; import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL; import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE; @@ -63,8 +65,9 @@ import java.util.function.Consumer; * the PIP. */ public class PipTouchHandler { + @VisibleForTesting static final float MINIMUM_SIZE_PERCENT = 0.4f; + private static final String TAG = "PipTouchHandler"; - private static final float MINIMUM_SIZE_PERCENT = 0.4f; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; // Allow PIP to resize to a slightly bigger state upon touch @@ -809,7 +812,6 @@ public class PipTouchHandler { } if (touchState.startedDragging()) { - mPipBoundsState.setStashed(STASH_TYPE_NONE); mSavedSnapFraction = -1f; mPipDismissTargetHandler.showDismissTargetMaybe(); } @@ -862,10 +864,10 @@ public class PipTouchHandler { // Reset the touch state on up before the fling settles mTouchState.reset(); - if (mEnableStash && !mPipBoundsState.isStashed() - && shouldStash(vel, getPossiblyMotionBounds())) { - mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */); + if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { + mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); } else { + mPipBoundsState.setStashed(STASH_TYPE_NONE); mMotionHelper.flingToSnapTarget(vel.x, vel.y, this::flingEndAction /* endAction */); } @@ -876,6 +878,9 @@ public class PipTouchHandler { < mPipBoundsState.getMaxSize().x && mPipBoundsState.getBounds().height() < mPipBoundsState.getMaxSize().y; + if (mMenuController.isMenuVisible()) { + mMenuController.hideMenu(false /* animate */, false /* resize */); + } if (toExpand) { mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); animateToMaximizedState(null); @@ -916,6 +921,11 @@ public class PipTouchHandler { && mPipExclusionBoundsChangeListener.get() != null) { mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds()); } + if (mPipBoundsState.getBounds().left < 0) { + mPipBoundsState.setStashed(STASH_TYPE_LEFT); + } else { + mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + } } private void flingEndAction() { @@ -933,12 +943,12 @@ public class PipTouchHandler { private boolean shouldStash(PointF vel, Rect motionBounds) { // If user flings the PIP window above the minimum velocity, stash PIP. - // Only allow stashing to the edge if the user starts dragging the PIP from the - // opposite edge. + // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite + // edge. final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold - && mDownSavedFraction > 1f && mDownSavedFraction < 2f) + && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) || (vel.x > mStashVelocityThreshold - && mDownSavedFraction > 3f && mDownSavedFraction < 4f)); + && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT)); // If User releases the PIP window while it's out of the display bounds, put // PIP into stashed mode. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 4f4e7dafe5c0..2b0a0cd3de20 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 @@ import com.android.internal.protolog.common.IProtoLogGroup; 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 66ecf453c362..552ebde05274 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 com.android.wm.shell.R; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import org.json.JSONException; @@ -109,10 +110,10 @@ public class ShellProtoLogImpl extends BaseProtoLogImpl { 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 e47e1ac71c73..9094d7de8d63 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 IBinder mLastActivityToken; - private PopupWindow mShowingHint; + private SizeCompatUILayout mLayout; + private ImageButton mRestartButton; + @VisibleForTesting + PopupWindow mShowingHint; + private WindowManager.LayoutParams mWinParams; - 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); + } - 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()); + public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); } - void updateLastTargetActivity(IBinder activityToken) { - mLastActivityToken = activityToken; + public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } - /** @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 inject(SizeCompatUILayout layout) { + mLayout = layout; + mWinParams = layout.getWindowLayoutParams(); } void remove() { - if (mShowingHint != null) { - mShowingHint.dismiss(); - } - getContext().getSystemService(WindowManager.class).removeViewImmediate(this); + 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); + 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 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene @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 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene 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 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene final Button gotItButton = popupView.findViewById(R.id.got_it); gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY), null /* content */, null /* mask */)); - gotItButton.setOnClickListener(view -> popupWindow.dismiss()); - popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY); + gotItButton.setOnClickListener(view -> dismissHint()); + popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX, + mLayout.mPopupOffsetY); } - private static int getGravity(int layoutDirection) { - return Gravity.BOTTOM - | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END); + void dismissHint() { + if (mShowingHint != null) { + mShowingHint.dismiss(); + mShowingHint = null; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java index 48ee86c4954f..a3880f497ff3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -18,134 +18,166 @@ package com.android.wm.shell.sizecompatui; import android.annotation.Nullable; import android.content.Context; -import android.graphics.Rect; +import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Display; -import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; /** - * Shows a restart-activity button on Task when the foreground activity is in size compatibility - * mode. + * Controls to show/update restart-activity buttons on Tasks based on whether the foreground + * activities are in size compatibility mode. */ public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener, DisplayImeController.ImePositionProcessor { - private static final String TAG = "SizeCompatUI"; + private static final String TAG = "SizeCompatUIController"; + + /** Whether the IME is shown on display id. */ + private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); /** The showing buttons by task id. */ - private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1); + private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0); + /** Avoid creating display context frequently for non-default display. */ private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); - @VisibleForTesting private final Context mContext; - private final ShellExecutor mMainExecutor; private final DisplayController mDisplayController; private final DisplayImeController mImeController; + private final SyncTransactionQueue mSyncQueue; /** Only show once automatically in the process life. */ private boolean mHasShownHint; - @VisibleForTesting public SizeCompatUIController(Context context, DisplayController displayController, DisplayImeController imeController, - ShellExecutor mainExecutor) { + SyncTransactionQueue syncQueue) { mContext = context; - mMainExecutor = mainExecutor; mDisplayController = displayController; mImeController = imeController; + mSyncQueue = syncQueue; mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); } - public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, - @Nullable IBinder sizeCompatActivity, + /** + * Called when the Task info changed. Creates and updates the restart button if there is an + * activity in size compat, or removes the restart button if there is no size compat activity. + * + * @param displayId display the task and activity are in. + * @param taskId task the activity is in. + * @param taskConfig task config to place the restart button with. + * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the + * top activity in this Task is not in size compat. + * @param taskListener listener to handle the Task Surface placement. + */ + public void onSizeCompatInfoChanged(int displayId, int taskId, + @Nullable Configuration taskConfig, @Nullable IBinder sizeCompatActivity, @Nullable ShellTaskOrganizer.TaskListener taskListener) { - // TODO Draw button on Task surface - if (taskBounds == null || sizeCompatActivity == null || taskListener == null) { + if (taskConfig == null || sizeCompatActivity == null || taskListener == null) { // Null token means the current foreground activity is not in size compatibility mode. - removeRestartButton(taskId); + removeLayout(taskId); + } else if (mActiveLayouts.contains(taskId)) { + // Button already exists, update the button layout. + updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener); } else { - updateRestartButton(displayId, taskId, sizeCompatActivity); + // Create a new restart button. + createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener); } } @Override public void onDisplayRemoved(int displayId) { mDisplayContextCache.remove(displayId); - for (int i = 0; i < mActiveButtons.size(); i++) { - final int taskId = mActiveButtons.keyAt(i); - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button != null && button.mDisplayId == displayId) { - removeRestartButton(taskId); - } + + // Remove all buttons on the removed display. + final List<Integer> toRemoveTaskIds = new ArrayList<>(); + forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId())); + for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) { + removeLayout(toRemoveTaskIds.get(i)); } } @Override - public void onImeVisibilityChanged(int displayId, boolean isShowing) { - final int newVisibility = isShowing ? View.GONE : View.VISIBLE; - for (int i = 0; i < mActiveButtons.size(); i++) { - final int taskId = mActiveButtons.keyAt(i); - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button == null || button.mDisplayId != displayId) { - continue; - } + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); + forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout)); + } - // Hide the button when input method is showing. - if (button.getVisibility() != newVisibility) { - button.setVisibility(newVisibility); - } + @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 void updateRestartButton(int displayId, int taskId, IBinder activityToken) { - SizeCompatRestartButton restartButton = mActiveButtons.get(taskId); - if (restartButton != null) { - restartButton.updateLastTargetActivity(activityToken); - return; - } + private boolean isImeShowingOnDisplay(int displayId) { + return mDisplaysWithIme.contains(displayId); + } + private void createLayout(int displayId, int taskId, Configuration taskConfig, + IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener) { final Context context = getOrCreateDisplayContext(displayId); if (context == null) { - Log.i(TAG, "Cannot get context for display " + displayId); + Log.e(TAG, "Cannot get context for display " + displayId); return; } - restartButton = createRestartButton(context, displayId); - restartButton.updateLastTargetActivity(activityToken); - if (restartButton.show()) { - mActiveButtons.append(taskId, restartButton); - } else { - onDisplayRemoved(displayId); - } + final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig, + activityToken, taskListener); + mActiveLayouts.put(taskId, layout); + layout.createSizeCompatButton(isImeShowingOnDisplay(displayId)); } @VisibleForTesting - SizeCompatRestartButton createRestartButton(Context context, int displayId) { - final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId, + SizeCompatUILayout createLayout(Context context, int displayId, int taskId, + Configuration taskConfig, IBinder activityToken, + ShellTaskOrganizer.TaskListener taskListener) { + final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, context, taskConfig, + taskId, activityToken, taskListener, mDisplayController.getDisplayLayout(displayId), mHasShownHint); // Only show hint for the first time. mHasShownHint = true; - return button; + return layout; } - private void removeRestartButton(int taskId) { - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button != null) { - button.remove(); - mActiveButtons.remove(taskId); + private void updateLayout(int taskId, Configuration taskConfig, + IBinder sizeCompatActivity, + ShellTaskOrganizer.TaskListener taskListener) { + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout == null) { + return; + } + layout.updateSizeCompatInfo(taskConfig, sizeCompatActivity, taskListener, + isImeShowingOnDisplay(layout.getDisplayId())); + } + + private void removeLayout(int taskId) { + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout != null) { + layout.release(); + mActiveLayouts.remove(taskId); } } @@ -167,4 +199,14 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } return context; } + + private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) { + for (int i = 0; i < mActiveLayouts.size(); i++) { + final int taskId = mActiveLayouts.keyAt(i); + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout != null && layout.getDisplayId() == displayId) { + callback.accept(layout); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java new file mode 100644 index 000000000000..5924b53f822c --- /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 000000000000..a7ad982a4736 --- /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 653299326cd0..10c742b69578 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 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + @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 f3f2fc3686b6..45d551528940 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.Drawable; 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 @@ class SplashscreenContentDrawer { // 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 final int mMaxIconAnimationDuration; + private int mIconSize; + private int mBrandingImageWidth; + private int mBrandingImageHeight; - SplashscreenContentDrawer(Context context) { + 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 @@ class SplashscreenContentDrawer { 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(); + } + final int animationDuration; + final Drawable iconDrawable; + if (attrs.mReplaceIcon != null) { + iconDrawable = attrs.mReplaceIcon; + animationDuration = Math.max(0, + Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration)); } else { - themeBGDrawable = context.getDrawable(resId); + iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) + : context.getPackageManager().getDefaultActivityIcon(); + animationDuration = 0; } - final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) - : context.getPackageManager().getDefaultActivityIcon(); // 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { 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 @@ class SplashscreenContentDrawer { } } } - - 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 f3749220d4e1..5332291fd1bd 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.util.SparseArray; 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 @@ import java.util.function.Consumer; * 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 @@ public class StartingSurfaceDrawer { 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 class StartingSurfaceDrawer { 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 @@ public class StartingSurfaceDrawer { // 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 @@ public class StartingSurfaceDrawer { } 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 @@ public class StartingSurfaceDrawer { } 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 @@ public class StartingSurfaceDrawer { + 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 @@ public class StartingSurfaceDrawer { 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 @@ public class StartingSurfaceDrawer { removeWindowSynced(taskId); } + /** + * 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); + } + 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); + 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 */); - mStartingWindowRecords.put(taskId, tView); - } + 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 @@ public class StartingSurfaceDrawer { 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/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 176b33dda020..a0e9f43218f2 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.assertFalse; 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; @@ -52,6 +53,7 @@ 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.SizeCompatUIController; +import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import org.junit.Before; import org.junit.Test; @@ -77,6 +79,8 @@ public class ShellTaskOrganizerTests { private Context mContext; @Mock private SizeCompatUIController mSizeCompatUI; + @Mock + private StartingSurfaceDrawer mStartingSurfaceDrawer; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -112,7 +116,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI)); + mSizeCompatUI, mStartingSurfaceDrawer)); } @Test @@ -279,10 +283,10 @@ public class ShellTaskOrganizerTests { // 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 @@ public class ShellTaskOrganizerTests { taskInfo2.topActivityInSizeCompat = true; mOrganizer.onTaskInfoChanged(taskInfo2); verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, - taskInfo1.configuration.windowConfiguration.getBounds(), - taskInfo1.topActivityToken, - taskListener); + taskInfo1.configuration, taskInfo1.topActivityToken, taskListener); + clearInvocations(mSizeCompatUI); mOrganizer.onTaskVanished(taskInfo1); verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, - null /* taskConfig */, null /* sizeCompatActivity*/, - null /* taskListener */); + null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */); } private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 449ad88f6532..19930485047c 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.ArgumentMatchers.anyInt; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class PipTouchHandlerTest extends ShellTestCase { 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 @@ public class PipTouchHandlerTest extends ShellTestCase { 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 @@ public class PipTouchHandlerTest extends ShellTestCase { } @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()); - } - - @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); + .updateMinSize(mPipBounds.width(), mPipBounds.height()); - 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 @@ public class PipTouchHandlerTest extends ShellTestCase { 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 000000000000..d9086a6ccdc1 --- /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 0eb64e5963d1..806a90b7832a 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @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 info. + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockActivityToken, mMockTaskListener); + + verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig), + eq(mMockActivityToken), eq(mMockTaskListener)); - // Verify that the restart button is added with non-null size compat activity. - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + // Verify that the restart button is 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).show(); - verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken)); + 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()); - // Verify that the restart button is removed with null size compat activity. - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null); + mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig); - mShellMainExecutor.flushAll(); - verify(mMockButton).remove(); + verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); } @Test public void testChangeButtonVisibilityOnImeShowHide() { - final int taskId = 12; - final Rect taskBounds = new Rect(0, 0, 1000, 2000); - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + final Configuration taskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockActivityToken, mMockTaskListener); - mShellMainExecutor.flushAll(); - // Verify that the restart button is hidden when IME is visible. - doReturn(View.VISIBLE).when(mMockButton).getVisibility(); mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); - verify(mMockButton).setVisibility(eq(View.GONE)); + verify(mMockLayout).updateImeVisibility(true); - // Verify that the restart button is visible when IME is hidden. - doReturn(View.GONE).when(mMockButton).getVisibility(); mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); - verify(mMockButton).setVisibility(eq(View.VISIBLE)); + verify(mMockLayout).updateImeVisibility(false); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java new file mode 100644 index 000000000000..236db44bdce0 --- /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 c9537afa37ef..de7d6c74bb06 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.testing.TestableContext; 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 @@ public class StartingSurfaceDrawerTests { @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 @@ public class StartingSurfaceDrawerTests { 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 @@ public class StartingSurfaceDrawerTests { 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 74cf1fda1b75..20a8a8c71a0f 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -160,10 +160,6 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { } } - if (!mHasStartValue) { - doSetStartValue(getValue(mTarget)); - } - if (!mStagingRequests.empty()) { // No interpolator was set, use the default if (mPlayState == PlayState::NotStarted && !mInterpolator) { @@ -270,9 +266,11 @@ bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { // to call setValue even if the animation isn't yet running or is still // being delayed as we need to override the staging value if (playTime < 0) { - setValue(mTarget, mFromValue); return false; } + if (!this->mHasStartValue) { + doSetStartValue(getValue(mTarget)); + } float fraction = 1.0f; if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) { diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index fa1752cc47d6..a48d7f734e29 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -64,8 +64,8 @@ static jlong createBitmapEffect( 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 @@ int register_android_graphics_RenderEffect(JNIEnv* env) { android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect", gRenderEffectMethods, NELEM(gRenderEffectMethods)); return 0; -}
\ No newline at end of file +} diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 4966bfa1c1e9..df66981853bb 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_setSurface(JNIEnv* env, jobject clazz, } } +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 @@ static const JNINativeMethod gMethods[] = { {"nSetName", "(JLjava/lang/String;)V", (void*)android_view_ThreadedRenderer_setName}, {"nSetSurface", "(JLandroid/view/Surface;Z)V", (void*)android_view_ThreadedRenderer_setSurface}, + {"nSetSurfaceControl", "(JJ)V", + (void*)android_view_ThreadedRenderer_setSurfaceControl}, {"nPause", "(J)Z", (void*)android_view_ThreadedRenderer_pause}, {"nSetStopped", "(JZ)V", (void*)android_view_ThreadedRenderer_setStopped}, {"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha}, diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 65afcc3a2558..9543d47640e6 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -133,6 +133,7 @@ void CanvasContext::removeRenderNode(RenderNode* node) { void CanvasContext::destroy() { stopDrawing(); setSurface(nullptr); + setSurfaceControl(nullptr); freePrefetchedLayers(); destroyHardwareResources(); mAnimationContext->destroy(); @@ -173,6 +174,19 @@ void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { 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 b31883b9ae94..917b00cb6591 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -112,6 +112,7 @@ public: 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 @@ private: 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 0ade8dde12eb..e14842f89b61 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -84,6 +84,19 @@ void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { }); } +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 a4adb16a930e..366d6b5a172c 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 @@ public: 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 7750a31b817f..26101867c43f 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 bool gHasRenderThreadInstance = false; 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 4fbb07168ac0..bb7c5181e112 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -78,6 +78,16 @@ struct VsyncSource { 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 @@ public: 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 @@ private: sk_sp<GrDirectContext> mGrContext; CacheManager* mCacheManager; sp<VulkanManager> mVkManager; + + ASurfaceControlFunctions mASurfaceControlFunctions; }; } /* namespace renderthread */ diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index a7e9a0de538a..5e2e5595ba9c 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -569,7 +569,12 @@ public class Location implements Parcelable { /** @hide */ public long getElapsedRealtimeAgeMillis() { - return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos()); + return getElapsedRealtimeAgeMillis(SystemClock.elapsedRealtime()); + } + + /** @hide */ + public long getElapsedRealtimeAgeMillis(long referenceRealtimeMs) { + return referenceRealtimeMs - NANOSECONDS.toMillis(mElapsedRealtimeNanos); } /** diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8d090f824e71..d896c1fc82b5 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -567,6 +567,25 @@ public class AudioManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int FLAG_FROM_KEY = 1 << 12; + /** @hide */ + @IntDef(flag = false, prefix = "FLAG", value = { + FLAG_SHOW_UI, + FLAG_ALLOW_RINGER_MODES, + FLAG_PLAY_SOUND, + FLAG_REMOVE_SOUND_AND_VIBRATE, + FLAG_VIBRATE, + FLAG_FIXED_VOLUME, + FLAG_BLUETOOTH_ABS_VOLUME, + FLAG_SHOW_SILENT_HINT, + FLAG_HDMI_SYSTEM_AUDIO_VOLUME, + FLAG_ACTIVE_MEDIA_ONLY, + FLAG_SHOW_UI_WARNINGS, + FLAG_SHOW_VIBRATE_HINT, + FLAG_FROM_KEY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Flags {} + // The iterator of TreeMap#entrySet() returns the entries in ascending key order. private static final TreeMap<Integer, String> FLAG_NAMES = new TreeMap<>(); diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 27f72687ccbe..ede1dbf47d36 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -181,6 +181,21 @@ public final class AudioPlaybackConfiguration implements Parcelable { @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 e056d435198a..7fb83f17a9d4 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2726,8 +2726,10 @@ public class AudioTrack extends PlayerBase } } synchronized(mPlayStateLock) { + baseStart(0); // unknown device at this point native_start(); - baseStart(native_getRoutedDeviceId()); + // FIXME see b/179218630 + //baseStart(native_getRoutedDeviceId()); if (mPlayState == PLAYSTATE_PAUSED_STOPPING) { mPlayState = PLAYSTATE_STOPPING; } else { diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 92db946ab5ba..44f8385b715e 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -23,6 +23,7 @@ import android.graphics.ImageFormat; 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 @@ public class ImageWriter implements AutoCloseable { if (format == ImageFormat.UNKNOWN) { format = SurfaceUtils.getSurfaceFormat(surface); } + // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native + // allocation estimation sequence depends on the public formats values. To avoid + // possible errors, convert where necessary. + if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) { + int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface); + switch (surfaceDataspace) { + case StreamConfigurationMap.HAL_DATASPACE_DEPTH: + format = ImageFormat.DEPTH_POINT_CLOUD; + break; + case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH: + format = ImageFormat.DEPTH_JPEG; + break; + case StreamConfigurationMap.HAL_DATASPACE_HEIF: + format = ImageFormat.HEIC; + break; + default: + format = ImageFormat.JPEG; + } + } // Estimate the native buffer allocation size and register it so it gets accounted for // during GC. Note that this doesn't include the buffers required by the buffer queue // itself and the buffers requested by the producer. diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index ca0d29f2f47f..2c45ed3cb861 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1356,6 +1356,7 @@ public class MediaPlayer extends PlayerBase } private void startImpl() { + baseStart(0); // unknown device at this point stayAwake(true); _start(); } @@ -1381,6 +1382,7 @@ public class MediaPlayer extends PlayerBase public void stop() throws IllegalStateException { stayAwake(false); _stop(); + baseStop(); } private native void _stop() throws IllegalStateException; @@ -1394,6 +1396,7 @@ public class MediaPlayer extends PlayerBase public void pause() throws IllegalStateException { stayAwake(false); _pause(); + basePause(); } private native void _pause() throws IllegalStateException; @@ -3479,7 +3482,8 @@ public class MediaPlayer extends PlayerBase case MEDIA_STOPPED: { tryToDisableNativeRoutingCallback(); - baseStop(); + // FIXME see b/179218630 + //baseStop(); TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onStopped(); @@ -3489,15 +3493,17 @@ public class MediaPlayer extends PlayerBase case MEDIA_STARTED: { - baseStart(native_getRoutedDeviceId()); + // FIXME see b/179218630 + //baseStart(native_getRoutedDeviceId()); tryToEnableNativeRoutingCallback(); } // fall through case MEDIA_PAUSED: { - if (msg.what == MEDIA_PAUSED) { - basePause(); - } + // FIXME see b/179218630 + //if (msg.what == MEDIA_PAUSED) { + // basePause(); + //} TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onPaused(msg.what == MEDIA_PAUSED); diff --git a/media/java/android/media/metrics/Event.java b/media/java/android/media/metrics/Event.java new file mode 100644 index 000000000000..5646dcdb6c9c --- /dev/null +++ b/media/java/android/media/metrics/Event.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +import android.annotation.IntRange; + +/** + * Abstract class for metrics events. + */ +public abstract class Event { + private final long mTimeSinceCreatedMillis; + + // hide default constructor + /* package */ Event() { + mTimeSinceCreatedMillis = MediaMetricsManager.INVALID_TIMESTAMP; + } + + protected Event(long timeSinceCreatedMillis) { + mTimeSinceCreatedMillis = timeSinceCreatedMillis; + } + + /** + * Gets time since the corresponding instance is created in millisecond. + * @return the timestamp since the instance is created, or -1 if unknown. + */ + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; + } +} diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java index f2eae5fad3c3..de780f672b28 100644 --- a/media/java/android/media/metrics/MediaMetricsManager.java +++ b/media/java/android/media/metrics/MediaMetricsManager.java @@ -26,7 +26,8 @@ import android.os.RemoteException; */ @SystemService(Context.MEDIA_METRICS_SERVICE) public class MediaMetricsManager { - // TODO: unhide APIs. + public static final long INVALID_TIMESTAMP = -1; + private static final String TAG = "MediaMetricsManager"; private IMediaMetricsManager mService; diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java index 3056e9820f9d..7f3450bcd12f 100644 --- a/media/java/android/media/metrics/PlaybackSession.java +++ b/media/java/android/media/metrics/PlaybackSession.java @@ -69,9 +69,8 @@ public final class PlaybackSession implements AutoCloseable { /** * Reports playback state event. - * @hide */ - public void reportPlaybackStateEvent(PlaybackStateEvent event) { + public void reportPlaybackStateEvent(@NonNull PlaybackStateEvent event) { mManager.reportPlaybackStateEvent(mId, event); } diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java index 6ce5bf0f0f33..8ca5b75dec98 100644 --- a/media/java/android/media/metrics/PlaybackStateEvent.java +++ b/media/java/android/media/metrics/PlaybackStateEvent.java @@ -17,6 +17,7 @@ package android.media.metrics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -27,10 +28,8 @@ import java.util.Objects; /** * Playback state event. - * @hide */ -public final class PlaybackStateEvent implements Parcelable { - // TODO: more states +public final class PlaybackStateEvent extends Event implements Parcelable { /** Playback has not started (initial state) */ public static final int STATE_NOT_STARTED = 0; /** Playback is buffering in the background for initial playback start */ @@ -41,23 +40,57 @@ public final class PlaybackStateEvent implements Parcelable { public static final int STATE_PLAYING = 3; /** Playback is paused but ready to play */ public static final int STATE_PAUSED = 4; - - private int mState; - private long mTimeSincePlaybackCreatedMillis; + /** Playback is handling a seek. */ + public static final int STATE_SEEKING = 5; + /** Playback is buffering to resume active playback. */ + public static final int STATE_BUFFERING = 6; + /** Playback is buffering while paused. */ + public static final int STATE_PAUSED_BUFFERING = 7; + /** Playback is suppressed (e.g. due to audio focus loss). */ + public static final int STATE_SUPPRESSED = 9; + /** + * Playback is suppressed (e.g. due to audio focus loss) while buffering to resume a playback. + */ + public static final int STATE_SUPPRESSED_BUFFERING = 10; + /** Playback has reached the end of the media. */ + public static final int STATE_ENDED = 11; + /** Playback is stopped and can be restarted. */ + public static final int STATE_STOPPED = 12; + /** Playback is stopped due a fatal error and can be retried. */ + public static final int STATE_FAILED = 13; + /** Playback is interrupted by an ad. */ + public static final int STATE_INTERRUPTED_BY_AD = 14; + /** Playback is abandoned before reaching the end of the media. */ + public static final int STATE_ABANDONED = 15; + + private final int mState; + private final long mTimeSinceCreatedMillis; // These track ExoPlayer states. See the ExoPlayer documentation for the state transitions. + /** @hide */ @IntDef(prefix = "STATE_", value = { STATE_NOT_STARTED, STATE_JOINING_BACKGROUND, STATE_JOINING_FOREGROUND, STATE_PLAYING, - STATE_PAUSED + STATE_PAUSED, + STATE_SEEKING, + STATE_BUFFERING, + STATE_PAUSED_BUFFERING, + STATE_SUPPRESSED, + STATE_SUPPRESSED_BUFFERING, + STATE_ENDED, + STATE_STOPPED, + STATE_FAILED, + STATE_INTERRUPTED_BY_AD, + STATE_ABANDONED, }) @Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface State {} /** * Converts playback state to string. + * @hide */ public static String stateToString(@State int value) { switch (value) { @@ -71,6 +104,26 @@ public final class PlaybackStateEvent implements Parcelable { return "STATE_PLAYING"; case STATE_PAUSED: return "STATE_PAUSED"; + case STATE_SEEKING: + return "STATE_SEEKING"; + case STATE_BUFFERING: + return "STATE_BUFFERING"; + case STATE_PAUSED_BUFFERING: + return "STATE_PAUSED_BUFFERING"; + case STATE_SUPPRESSED: + return "STATE_SUPPRESSED"; + case STATE_SUPPRESSED_BUFFERING: + return "STATE_SUPPRESSED_BUFFERING"; + case STATE_ENDED: + return "STATE_ENDED"; + case STATE_STOPPED: + return "STATE_STOPPED"; + case STATE_FAILED: + return "STATE_FAILED"; + case STATE_INTERRUPTED_BY_AD: + return "STATE_INTERRUPTED_BY_AD"; + case STATE_ABANDONED: + return "STATE_ABANDONED"; default: return Integer.toHexString(value); } @@ -83,14 +136,13 @@ public final class PlaybackStateEvent implements Parcelable { */ public PlaybackStateEvent( int state, - long timeSincePlaybackCreatedMillis) { + long timeSinceCreatedMillis) { + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; this.mState = state; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; } /** * Gets playback state. - * @return */ public int getState() { return mState; @@ -98,9 +150,12 @@ public final class PlaybackStateEvent implements Parcelable { /** * Gets time since the corresponding playback is created in millisecond. + * @return the timestamp since the playback is created, or -1 if unknown. */ - public long getTimeSincePlaybackCreatedMillis() { - return mTimeSincePlaybackCreatedMillis; + @Override + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; } @Override @@ -109,18 +164,18 @@ public final class PlaybackStateEvent implements Parcelable { if (o == null || getClass() != o.getClass()) return false; PlaybackStateEvent that = (PlaybackStateEvent) o; return mState == that.mState - && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis; + && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis; } @Override public int hashCode() { - return Objects.hash(mState, mTimeSincePlaybackCreatedMillis); + return Objects.hash(mState, mTimeSinceCreatedMillis); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mState); - dest.writeLong(mTimeSincePlaybackCreatedMillis); + dest.writeLong(mTimeSinceCreatedMillis); } @Override @@ -131,10 +186,10 @@ public final class PlaybackStateEvent implements Parcelable { /** @hide */ /* package-private */ PlaybackStateEvent(@NonNull Parcel in) { int state = in.readInt(); - long timeSincePlaybackCreatedMillis = in.readLong(); + long timeSinceCreatedMillis = in.readLong(); this.mState = state; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; } public static final @NonNull Parcelable.Creator<PlaybackStateEvent> CREATOR = @@ -150,4 +205,43 @@ public final class PlaybackStateEvent implements Parcelable { } }; + /** + * A builder for {@link PlaybackStateEvent} + */ + public static final class Builder { + private int mState = STATE_NOT_STARTED; + private long mTimeSinceCreatedMillis = -1; + + /** + * Creates a new Builder. + */ + public Builder() { + } + + /** + * Sets playback state. + */ + public @NonNull Builder setState(@State int value) { + mState = value; + return this; + } + + /** + * Sets timestamp since the creation in milliseconds. + * @param value the timestamp since the creation in milliseconds. + * -1 indicates the value is unknown. + */ + public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) { + mTimeSinceCreatedMillis = value; + return this; + } + + /** Builds the instance. */ + public @NonNull PlaybackStateEvent build() { + PlaybackStateEvent o = new PlaybackStateEvent( + mState, + mTimeSinceCreatedMillis); + return o; + } + } } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index f580ea5d57de..13a3436569aa 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -1127,9 +1127,10 @@ public final class MediaSessionManager { * toast showing the volume should be shown. * * @param sessionToken the remote media session token - * @param flags extra information about how to handle the volume change + * @param flags flags containing extra action or information regarding the volume change */ - void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags); + void onVolumeChanged(@NonNull MediaSession.Token sessionToken, + @AudioManager.Flags int flags); /** * Called when the default remote session is changed where the default remote session diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp index 748d45808932..359ef364083c 100644 --- a/media/jni/tuner/DemuxClient.cpp +++ b/media/jni/tuner/DemuxClient.cpp @@ -216,14 +216,13 @@ Result DemuxClient::disconnectCiCam() { 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 c9bacda0fa70..07be5cf33764 100644 --- a/media/jni/tuner/DescramblerClient.cpp +++ b/media/jni/tuner/DescramblerClient.cpp @@ -101,14 +101,18 @@ Result DescramblerClient::removePid(DemuxPid pid, sp<FilterClient> optionalSourc 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 04004858aaee..779318008a9b 100644 --- a/media/jni/tuner/DvrClient.cpp +++ b/media/jni/tuner/DvrClient.cpp @@ -316,14 +316,13 @@ Result DvrClient::flush() { 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 f61889035432..f31d4651350a 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -262,14 +262,14 @@ Result FilterClient::close() { if (mTunerFilter != NULL) { Status s = mTunerFilter->close(); closeAvSharedMemory(); + mTunerFilter = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mFilter != NULL) { Result res = mFilter->close(); - if (res == Result::SUCCESS) { - mFilter = NULL; - } + mFilter = NULL; + mFilter_1_1 = NULL; closeAvSharedMemory(); return res; } diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index 0613223bd5dc..9e3664275dac 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -322,15 +322,14 @@ Result FrontendClient::unlinkCiCamToFrontend(int ciCamId) { Result FrontendClient::close() { if (mTunerFrontend != NULL) { Status s = mTunerFrontend->close(); + mTunerFrontend = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mFrontend != NULL) { Result result = mFrontend->close(); - if (result == Result::SUCCESS) { - mFrontend = NULL; - mFrontend_1_1 = NULL; - } + mFrontend = NULL; + mFrontend_1_1 = NULL; return result; } diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp index 8d08c2586b48..5b6e46eba418 100644 --- a/media/jni/tuner/LnbClient.cpp +++ b/media/jni/tuner/LnbClient.cpp @@ -113,11 +113,14 @@ Result LnbClient::sendDiseqcMessage(vector<uint8_t> diseqcMessage) { 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 432238d261e5..e123c9f57ce7 100644 --- a/media/jni/tuner/TimeFilterClient.cpp +++ b/media/jni/tuner/TimeFilterClient.cpp @@ -126,11 +126,14 @@ long TimeFilterClient::getSourceTime() { Result TimeFilterClient::close() { if (mTunerTimeFilter != NULL) { Status s = mTunerTimeFilter->close(); + mTunerTimeFilter = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mTimeFilter != NULL) { - return mTimeFilter->close(); + Result res = mTimeFilter->close(); + mTimeFilter = NULL; + return res; } return Result::INVALID_STATE; diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311c5..92a792b78410 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // 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 0a02e3a38043..92d7bf06aa9e 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -824,6 +824,7 @@ public class ConnectivityManager { @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 @@ public class ConnectivityManager { } /** - * 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 @@ public class ConnectivityManager { } /** + * 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 @@ public class ConnectivityManager { } /** - * 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 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4850,9 +4834,13 @@ public class ConnectivityManager { 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 */ diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index befd4fb03d71..6391802f3330 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -43,9 +43,6 @@ import android.os.PersistableBundle; 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 @@ -123,35 +120,8 @@ interface IConnectivityManager 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); @@ -200,10 +170,6 @@ interface IConnectivityManager 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, @@ -223,8 +189,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 9d67f0b84367..26d14cbfaa95 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -2085,9 +2085,10 @@ public final class NetworkCapabilities implements Parcelable { /** * 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 @@ public final class NetworkCapabilities implements Parcelable { } /** + * 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 b4a651c0607e..4e3085f4704d 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.os.Process; 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 @@ public class NetworkRequest implements Parcelable { * 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 @@ public class NetworkRequest implements Parcelable { // 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 class NetworkRequest implements Parcelable { */ 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 class NetworkRequest implements Parcelable { */ 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 @@ public class NetworkRequest implements Parcelable { @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 @@ public class NetworkRequest implements Parcelable { 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 index 082fa58f8ac2..0242ba08742c 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 com.android.internal.util.MessageUtils; 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 8fc318180778..ed1716fad8c0 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -25,7 +25,6 @@ cc_library_shared { ], 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 3afcb0e8f688..00128794bcd0 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } - if (register_android_server_connectivity_Vpn(env) < 0 - || register_android_server_TestNetworkService(env) < 0) { + if (register_android_server_TestNetworkService(env) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; } -};
\ No newline at end of file +}; diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 34e11528df62..577fb6611448 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 6c7f87777566..c554e2ed8a8e 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 e87939dc4474..b23cebd19e7a 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 6575f1a558d0..2380b2fa21ac 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 da4d512572f8..f70bb84c55a4 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 4c79265d2399..749f864f8aef 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 3a9141ffd9d7..6b2740d7d7d7 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 8f1a43c64a5d..1a24b9e52b36 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналът за данни е пълен."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Връзката с 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 afa6521b6f4b..1c9f84f68962 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"পূর্ণ ডেটার সংকেত রয়েছে৷"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথারনেটের সংযোগ বিচ্ছিন্ন হয়েছে৷"</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 ae4a15740007..f70142187a36 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 fd81f99b2182..78ef10969b72 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 e35bd0063d02..8ca9800562a0 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 0cba4ff7c593..5d55a12b4d6c 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 c476e68c9268..1fbb391741b5 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 49cd2e6c73b2..32bcf8792067 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 cad79a56a0fe..62419538309b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> - <skip /> + <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 b8083a0dcb19..198fee4b58af 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> - <skip /> + <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 cad79a56a0fe..62419538309b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> - <skip /> + <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 cad79a56a0fe..62419538309b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> - <skip /> + <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 d4dcf6649b9d..27cd8b34889e 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> @@ -592,4 +592,5 @@ <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="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 d7bdec149c3b..0f765764dbfd 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 87f5dee46a64..abd209ccc717 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 1b8f35d8619a..2e9f3b8948a2 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 d2603a57c4d7..ecb4f7573b66 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 118ef23b09b0..56ade54d9212 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 c9587c71bede..e26ecb705c9f 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 fc453885b840..d61ebc099934 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 99d02940656a..64c06fae175d 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 0ecb3a1ab00e..cf63dd7176b2 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 d753be9a5b6a..427ce56296fe 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 1a6f1920abf3..159d2dbe7439 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूरा."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ईथरनेट डिस्कनेक्ट किया गया."</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 7846212ff18d..5e33ef3d0689 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 99c8527880db..28975be7b6fb 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 6e4ece87a2cf..a2075265b58c 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 e71ccb5eb339..c4d752d25cf3 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 3605c091adff..56b0a81b7c81 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 941fa890eefd..f045d273ce0d 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> @@ -591,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 835e98a945d5..ba3a46786663 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 b2e49d775afb..6f398b6840b0 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 bc53ac69946b..5bf1dd03f558 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 0a08e39bd044..c7c38378db6e 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 64ae7d75b6e2..99a73e14daee 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 9d00ba42ceea..54a42a8b0090 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ಡೇಟಾ ಸಂಕೇತ ತುಂಬಿದೆ."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</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 9bc7a59eee44..9e9fea9e57ad 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index e5418feab8c5..2aec5cb42199 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 2ed51313f68e..e199e1f7db83 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 084df081c750..a25c59f5eb26 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 8633366a3434..f360c906d95a 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 ef595a7bb336..a32d00b00928 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналот за податоци е исполнет."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Етернетот е исклучен."</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 3d0a27473e5a..902507df89c8 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ഡാറ്റ സിഗ്നൽ പൂർണ്ണമാണ്."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ഇതർനെറ്റ് വിച്ഛേദിച്ചു."</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 0722e3fe5900..83009b7c03a1 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 166a2825a7af..359f52565934 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूर्ण."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट डिस्कनेक्ट केले."</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 a5913dd5148c..eba89daf02ed 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 37aaa09d63f4..e982aa93d28e 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ဒေတာထုတ်လွှင့်မှုအပြည့်ဖမ်းမိခြင်း"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"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 4cf365ba52d9..abbb5e7c64ee 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 d022aa7c6438..d7a32bf3a038 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 777332279ecc..7e739bca9b94 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 72190c9bdf0c..1e842b7f7ce0 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 e5f95e707d85..d035fc0b5c9c 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">" ਡਾਟਾ ਸਿਗਨਲ ਪੂਰਾ।"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ਈਥਰਨੈੱਟ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ।"</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 794e41f4336e..8a732cb7710b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 765dbf548e93..a63b9b5660da 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> @@ -592,4 +592,6 @@ <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="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 8dcc21ac47bd..ab23059e013c 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 765dbf548e93..a63b9b5660da 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> @@ -592,4 +592,6 @@ <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="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 0c33d12cd93f..98edb32e2865 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 039a64ceb756..972dc205fea5 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 ed8cb4c00569..0dee8e1c8af9 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 209749f5a5ec..7d49ca3b91cd 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 88d06a167c47..48e5dcc2284b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 2ce275349589..0bc905e688a1 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 735d861de05f..6d19af89f47b 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 d4a7d1bc66aa..944c423b35ac 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 b1142d31bdea..442f54be37bb 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 fd17d13914e1..561a122430e7 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 2373b09cb4e6..18d7f281db62 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 b2843583fe31..ceab26cabd9a 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 855d2f5d27b8..671fa149219f 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 a1556a10aa16..98d89f3e43d5 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 8ee2b8e09af9..4e739acb53a7 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 827c80ab2691..c7dee9513759 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> @@ -592,4 +592,6 @@ <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ڈیٹا سگنل بھرا ہوا ہے۔"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ایتھرنیٹ منقطع ہے۔"</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 e4d0c33c52c8..433096b64f08 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 eb86a80aabab..a14462bf7efe 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 ab346830bddd..f6bea916c3fa 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 db5a5240f54b..3cf15a9f3995 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 7e73705eec85..7cf02978efd8 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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 f487db109e07..d950d58b5bf7 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,6 +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> - <!-- no translation found for accessibility_ethernet_connected (6175942685957461563) --> + <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/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 927da0ae8495..2b357c57b306 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -396,6 +396,28 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { } /** + * 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 64cb0f1b7885..43717aba3abd 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 @@ public class ConnectivitySubsystemsRecoveryManager { } 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 caabf9af35f6..1474f184775d 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.annotation.VisibleForTesting; 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 abstract class AbstractEnableAdbPreferenceController extends 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 abstract class AbstractEnableAdbPreferenceController extends 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 @@ public abstract class AbstractEnableAdbPreferenceController extends @Override public void updateState(Preference preference) { ((TwoStatePreference) preference).setChecked(isAdbEnabled()); + if (isAvailable()) { + ((RestrictedSwitchPreference) preference).setDisabledByAdmin( + checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId())); + } } public void enablePreference(boolean enabled) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java index e84a25c0ba4e..5f53a92c131e 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 @@ package com.android.settingslib.development; 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 @@ import org.robolectric.shadows.ShadowApplication; @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 @@ public class EnableAdbPreferenceControllerTest { @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 @@ public class EnableAdbPreferenceControllerTest { @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 a6e2af9b1674..9cd7083a2a11 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.app.backup.FullBackupDataOutput; 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { @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 @@ public class SettingsBackupAgent extends BackupAgentHelper { } 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 438cec8ef68f..6719f179ef86 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -323,6 +323,7 @@ public class SettingsBackupTest { 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 1566b761d23e..539a81bf43a0 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" /> @@ -402,6 +403,9 @@ <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" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7bfb42b7cfad..bf5198eadb9c 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -194,6 +194,5 @@ android_app { 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 5af024448987..2faca8dbdcbf 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -600,6 +600,14 @@ android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> + <!-- ContentProvider that returns a People Tile preview for a given shortcut --> + <provider + android:name="com.android.systemui.people.PeopleProvider" + android:authorities="com.android.systemui.people.PeopleProvider" + android:exported="true" + android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW"> + </provider> + <!-- a gallery of delicious treats --> <service android:name=".DessertCaseDream" diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java index d43aaf07c6be..beee03b52579 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 @@ public interface DetailAdapter { 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 000000000000..51c442abf2fd --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight" + android:radius="40dp"/> diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml new file mode 100644 index 000000000000..3938b73d08ff --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<!-- This is a view that shows a user switcher in Keyguard. --> +<com.android.systemui.statusbar.phone.UserAvatarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_qs_user_switch_view" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + android:layout_centerHorizontal="true" + android:layout_gravity="center_horizontal|bottom" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameColor="@color/kg_user_avatar_frame" + systemui:framePadding="0dp" + systemui:frameWidth="0dp"> +</com.android.systemui.statusbar.phone.UserAvatarView> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 859d9048cee3..a8ef7c346b95 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -32,6 +32,12 @@ android:visibility="gone" /> <ViewStub + android:id="@+id/keyguard_qs_user_switch_stub" + android:layout="@layout/keyguard_qs_user_switch" + android:layout_height="match_parent" + android:layout_width="match_parent" /> + + <ViewStub android:id="@+id/keyguard_user_switcher_stub" android:layout="@layout/keyguard_user_switcher" android:layout_height="match_parent" @@ -63,6 +69,13 @@ systemui:layout_constraintEnd_toEndOf="parent" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/qs_edge_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + systemui:layout_constraintGuide_percent="0.5" + android:orientation="vertical"/> + <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout android:id="@+id/notification_stack_scroller" android:layout_marginTop="@dimen/notification_panel_margin_top" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 61962256f93d..78927f8bf8d4 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -279,6 +279,11 @@ <!-- Whether to show the full screen user switcher. --> <bool name="config_enableFullscreenUserSwitcher">false</bool> + <!-- Whether the multi-user switch on the keyguard opens QS user panel. If false, clicking the + user switch on the keyguard will replace the notifications and status area with the user + switcher. The multi-user switch is only shown if config_keyguardUserSwitcher=false. --> + <bool name="config_keyguard_user_switch_opens_qs_details">false</bool> + <!-- SystemUIFactory component --> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 594fbdf55d3c..0d92aea6ed6f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -802,7 +802,7 @@ <!-- Size of user icon + frame in the qs user picker (incl. frame) --> <dimen name="qs_framed_avatar_size">54dp</dimen> <!-- Size of user icon + frame in the keyguard user picker (incl. frame) --> - <dimen name="kg_framed_avatar_size">32dp</dimen> + <dimen name="kg_framed_avatar_size">48dp</dimen> <!-- Margin on the left side of the carrier text on Keyguard --> <dimen name="keyguard_carrier_text_margin">16dp</dimen> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 6d731f8587b6..d4bb128120e9 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -38,5 +38,8 @@ <!-- People Tile flag --> <bool name="flag_conversations">false</bool> + <!-- The new animations to/from lockscreen and AOD! --> + <bool name="flag_lockscreen_animations">false</bool> + <bool name="flag_toast_style">false</bool> </resources> diff --git a/packages/SystemUI/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 000000000000..15cf3696fec7 --- /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 f6b03c1fa013..e4f6e131258e 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.util.AttributeSet; 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 @@ public class CarrierText extends TextView { } 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 @@ public class CarrierText extends TextView { } } - 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 b1e14346c3fa..46a6d8b82911 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); } - }; - - @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) == 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 - } - - /** - * 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); - } - }); - } - - private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - } - - /** - * 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); + @Override + public void finishedWakingUp() { + mView.setSelected(true); } - 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; - } + @Inject + public CarrierTextController(CarrierText view, + CarrierTextManager.Builder carrierTextManagerBuilder, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + super(view); - return mContext.getText(carrierHelpTextId); + mCarrierTextManager = carrierTextManagerBuilder + .setShowAirplaneMode(mView.getShowAirplaneMode()) + .setShowMissingSim(mView.getShowMissingSim()) + .build(); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; } - 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); - } + @Override + protected void onInit() { + super.onInit(); + mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive()); } - /** - * 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; - } + @Override + protected void onViewAttached() { + mCarrierTextManager.setListening(mCarrierTextCallback); } - /** - * 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 000000000000..87b01e8671f0 --- /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 707ee298a55a..c4b02f62f291 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 @@ import com.android.systemui.util.EmergencyDialerConstants; */ 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 class EmergencyButton extends Button { 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 @@ public class EmergencyButton extends Button { return false; }); } - whitelistIpcs(this::updateEmergencyCallButton); } @Override @@ -165,65 +107,13 @@ public class EmergencyButton extends Button { 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 @@ public class EmergencyButton extends Button { 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 @@ public class EmergencyButton extends Button { 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 000000000000..4275189cfe26 --- /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 5760565aaab1..7a05a17c8010 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.util.LatencyTracker; 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey @Override public void onInit() { + super.onInit(); mMessageAreaController.init(); } @@ -91,10 +94,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey 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 276036c400e1..76a7473e25e8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -36,7 +36,6 @@ import android.view.WindowManager; 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 java.util.concurrent.Executor; 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 @@ public class KeyguardDisplayManager { @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 @@ public class KeyguardDisplayManager { // 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 1c691e7098a1..a0c5958284ec 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.keyguard.KeyguardSecurityModel.SecurityMode; 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final SecurityMode mSecurityMode; private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final EmergencyButton mEmergencyButton; + private final EmergencyButtonController mEmergencyButtonController; private boolean mPaused; @@ -68,11 +70,18 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> }; 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> 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 0f1c3c8a20b7..2e4554592580 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -111,10 +111,11 @@ public class KeyguardPasswordViewController 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 2aaf748e2415..55e348cc06b1 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.LockPatternUtils; 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 @@ public class KeyguardPatternViewController 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 @@ public class KeyguardPatternViewController 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 @@ public class KeyguardPatternViewController 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 @@ public class KeyguardPatternViewController 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 6a6b964c2a8f..825ea2570df0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -167,6 +167,20 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } /** + * 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 f2479488db0f..1b5aa453ac97 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -71,9 +71,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB 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 c0aa2af00a74..a456d42f5be5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -34,11 +34,13 @@ public class KeyguardPinViewController 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 c77c86711abf..bacd29f661ae 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.SubscriptionManager; 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 @@ public class KeyguardSecurityModel { 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 f1b504e9f941..33d47fe13f38 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -44,15 +44,18 @@ public class KeyguardSecurityViewFlipperController 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 @@ public class KeyguardSecurityViewFlipperController if (childController == null) { childController = new NullKeyguardInputViewController( - securityMode, keyguardSecurityCallback); + securityMode, keyguardSecurityCallback, + mEmergencyButtonControllerFactory.create(null)); } return childController; @@ -140,8 +144,9 @@ public class KeyguardSecurityViewFlipperController 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 b21814134607..4d2ebbb4a594 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -78,13 +78,15 @@ public class KeyguardSimPinViewController 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 890a17c1cf92..0d9bb6f73b49 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -37,7 +37,6 @@ import android.widget.ImageView; 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 @@ public class KeyguardSimPukViewController 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 @@ public class KeyguardSimPukViewController 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 fb97a30f93fb..1fbf71de47ca 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -49,10 +49,8 @@ import androidx.slice.widget.SliceContent; 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 @@ public class KeyguardSliceView extends LinearLayout { 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 @@ public class KeyguardSliceView extends LinearLayout { * 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 @@ public class KeyguardSliceView extends LinearLayout { 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 1b0a7faeddab..8038ce4c7b69 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java @@ -83,6 +83,10 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie 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 fea152abe36a..5db4f9e61140 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -34,7 +34,6 @@ import android.widget.TextView; 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 @@ public class KeyguardStatusView extends GridLayout { 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 @@ public class KeyguardStatusView extends GridLayout { } } - void setCanShowLogout(boolean canShowLogout) { - mCanShowLogout = canShowLogout; - updateLogoutView(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -159,10 +152,7 @@ public class KeyguardStatusView extends GridLayout { mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged); onSliceContentChanged(); - boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); - setEnableMarquee(shouldMarquee); updateOwnerInfo(); - updateLogoutView(); updateDark(); } @@ -209,11 +199,11 @@ public class KeyguardStatusView extends GridLayout { 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 @@ public class KeyguardStatusView extends GridLayout { } } - 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 6fb6760be653..bfe7f8c7ebd8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import android.os.UserHandle; import android.util.Slog; import android.view.View; @@ -78,6 +79,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @Override public void onInit() { mKeyguardClockSwitchController.init(); + mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive()); + mView.updateLogoutView(shouldShowLogout()); } @Override @@ -245,6 +248,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } } + private boolean shouldShowLogout() { + return mKeyguardUpdateMonitor.isLogoutEnabled() + && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; + } + private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -271,12 +279,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardSliceViewController.updateTopMargin( mKeyguardClockSwitchController.getClockTextTopPadding()); mView.setCanShowOwnerInfo(false); - mView.setCanShowLogout(false); + mView.updateLogoutView(false); } else { // reset margin mKeyguardSliceViewController.updateTopMargin(0); mView.setCanShowOwnerInfo(true); - mView.setCanShowLogout(false); + mView.updateLogoutView(false); } updateAodIcons(); } @@ -302,7 +310,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); refreshTime(); mView.updateOwnerInfo(); - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } } @@ -320,12 +328,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV public void onUserSwitchComplete(int userId) { mKeyguardClockSwitchController.refreshFormat(); mView.updateOwnerInfo(); - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } @Override public void onLogoutEnabledChanged() { - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } }; } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 372810677649..886c3729124b 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 @@ public class NumPadButton extends AlphaOptimizedImageButton { } @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 @@ public class NumPadButton extends AlphaOptimizedImageButton { * 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 756d6107570a..01e1c632ad83 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.graphics.drawable.GradientDrawable; 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 @@ public class NumPadKey extends ViewGroup { 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 @@ public class NumPadKey extends ViewGroup { } /** + * 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 @@ public class NumPadKey extends ViewGroup { mDigitText.setTextColor(textColor); mKlondikeText.setTextColor(klondikeColor); - mAnimator.reloadColors(getContext()); + if (mAnimator != null) mAnimator.reloadColors(getContext()); } @Override @@ -150,14 +162,14 @@ public class NumPadKey extends ViewGroup { 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 @@ public class NumPadKey extends ViewGroup { 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 @@ public class NumPadKey extends ViewGroup { left = centerX - mKlondikeText.getMeasuredWidth() / 2; mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom); - mAnimator.onLayout(b - t); + if (mAnimator != null) mAnimator.onLayout(b - t); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java new file mode 100644 index 000000000000..c4be1ba53503 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.clock; + +import java.util.List; + +import dagger.Module; +import dagger.Provides; + +/** Dagger Module for clock package. */ +@Module +public abstract class ClockModule { + + /** */ + @Provides + public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) { + return clockManager.getClockInfos(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java index 5ef35be8df51..b6413cb61deb 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.text.TextUtils; 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 @@ public final class ClockOptionsProvider extends ContentProvider { 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 @@ public final class ClockOptionsProvider extends ContentProvider { } 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 @@ public final class ClockOptionsProvider extends ContentProvider { 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 000000000000..3a0357d2a284 --- /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 000000000000..49a617eeb6c0 --- /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 000000000000..a6725234e4af --- /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 000000000000..ba0642f57a88 --- /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 1b6476ce74df..d342377da49b 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 @@ import dagger.Subcomponent; /** * Subcomponent for helping work with KeyguardStatusView and its children. + * + * TODO: unify this with {@link KeyguardStatusBarViewComponent} */ @Subcomponent(modules = {KeyguardStatusViewModule.class}) @KeyguardStatusViewScope diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 71ec33e16e0e..b6a232d576b8 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.Log; 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 @@ public class ImageWallpaper extends WallpaperService { // 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 @@ public class ImageWallpaper extends WallpaperService { super.onDestroy(); mWorker.quitSafely(); mWorker = null; + mMiniBitmap = null; } class GLEngine extends Engine { @@ -80,7 +92,7 @@ public class ImageWallpaper extends WallpaperService { @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 @@ public class ImageWallpaper extends WallpaperService { 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 @@ public class ImageWallpaper extends WallpaperService { 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 @@ public class ImageWallpaper extends WallpaperService { @Override public void onDestroy() { + mMiniBitmap = null; mWorker.getThreadHandler().post(() -> { mRenderer.finish(); mRenderer = null; @@ -134,6 +167,61 @@ public class ImageWallpaper extends WallpaperService { }); } + + + @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 38a82f8c9908..36937d622b5b 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 @@ import javax.inject.Inject * 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 @@ class ControlsComponent @Inject constructor( Optional.empty() } } -}
\ No newline at end of file + + /** + * @return true if controls are feature-enabled and have available services to serve controls + */ + fun isEnabled() = featureEnabled && lazyControlsController.get().available + + /** + * Returns one of 3 states: + * * AVAILABLE - Controls can be made visible + * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock + * * UNAVAILABLE - Controls are not enabled + */ + fun getVisibility(): Visibility { + if (!isEnabled()) return Visibility.UNAVAILABLE + if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) + == STRONG_AUTH_REQUIRED_AFTER_BOOT) { + return Visibility.AVAILABLE_AFTER_UNLOCK + } + if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) { + return Visibility.AVAILABLE_AFTER_UNLOCK + } + + return Visibility.AVAILABLE + } + + private fun updateShowWhileLocked() { + canShowWhileLockedSetting = secureSettings.getInt( + Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0 + } + + enum class Visibility { + AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index ffb8446f3e21..91c2dcfd9202 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 @@ public interface SysUIComponent { * 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 b0067cd15c1b..b67db03a743c 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 android.content.Context; 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 @@ import dagger.Provides; @Module(includes = { AppOpsModule.class, AssistModule.class, + ClockModule.class, ControlsModule.class, DemoModeModule.class, FalsingModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 8af45a5c0ef1..d85b10167697 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.SOM 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; + mControlsComponent = controlsComponent; mControlsUiControllerOptional = controlsComponent.getControlsUiController(); mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; @@ -387,7 +391,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }); - 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 1a0356c4446d..01a353ce8f1f 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 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mWallpaper = new ImageGLWallpaper(mProgram); } + /** + * @hide + * @return + */ + public void useBitmap(Consumer<Bitmap> c) { + mTexture.use(c); + } + @Override public boolean isWcgContent() { return mTexture.isWcgContent(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 9e5b225fbefc..a747edd0580a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -29,6 +29,8 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -62,7 +64,11 @@ import dagger.Provides; /** * Dagger Module providing {@link StatusBar}. */ -@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, +@Module(subcomponents = { + KeyguardQsUserSwitchComponent.class, + KeyguardStatusBarViewComponent.class, + KeyguardStatusViewComponent.class, + KeyguardUserSwitcherComponent.class}, includes = {FalsingModule.class}) public class KeyguardModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java new file mode 100644 index 000000000000..e7458a3df801 --- /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 dd054848aed2..7eb1fc175c5a 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.ContactsContract; 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 @@ public class PeopleSpaceUtils { } /** 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 @@ public class PeopleSpaceUtils { 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 @@ public class PeopleSpaceUtils { 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 @@ public class PeopleSpaceUtils { .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 @@ public class PeopleSpaceUtils { * </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 @@ public class PeopleSpaceUtils { 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 619729e55314..9967936ac1bd 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 @@ public class QSDetail extends LinearLayout { 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 @@ public class QSDetail extends LinearLayout { 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 class QSDetail extends LinearLayout { 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 @@ public class QSDetail extends LinearLayout { } else { mTriggeredExpand = false; } + mShouldAnimate = adapter.shouldAnimate(); mOpenX = x; mOpenY = y; } else { @@ -190,10 +183,10 @@ public class QSDetail extends LinearLayout { } } - 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 @@ public class QSDetail extends LinearLayout { 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 @@ public class QSDetail extends LinearLayout { } 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 @@ public class QSDetail extends LinearLayout { } } + 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 @@ public class QSDetail extends LinearLayout { 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 7d87e174d95d..b50af004aff9 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 @@ public class QSDetailDisplayer { /** 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 782092161418..fcb35e2040ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -311,7 +311,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } /** */ - 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/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index a567f512b204..aa6bbbda04fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -34,7 +34,7 @@ import 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; @@ -58,7 +58,7 @@ public class QSCarrierGroupController { private final ActivityStarter mActivityStarter; private final Handler mBgHandler; private final NetworkController mNetworkController; - private final CarrierTextController mCarrierTextController; + private final CarrierTextManager mCarrierTextManager; private final TextView mNoSimTextView; private final H mMainHandler; private final Callback mCallback; @@ -153,7 +153,7 @@ public class QSCarrierGroupController { } }; - private static class Callback implements CarrierTextController.CarrierTextCallback { + private static class Callback implements CarrierTextManager.CarrierTextCallback { private H mHandler; Callback(H handler) { @@ -161,7 +161,7 @@ public class QSCarrierGroupController { } @Override - public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); } } @@ -169,7 +169,7 @@ public class QSCarrierGroupController { private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder, Context context) { + CarrierTextManager.Builder carrierTextManagerBuilder, Context context) { if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { mProviderModel = true; } else { @@ -178,7 +178,7 @@ public class QSCarrierGroupController { mActivityStarter = activityStarter; mBgHandler = bgHandler; mNetworkController = networkController; - mCarrierTextController = carrierTextControllerBuilder + mCarrierTextManager = carrierTextManagerBuilder .setShowAirplaneMode(false) .setShowMissingSim(false) .build(); @@ -196,7 +196,6 @@ public class QSCarrierGroupController { mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState); mCallback = new Callback(mMainHandler); - mCarrierGroups[0] = view.getCarrier1View(); mCarrierGroups[1] = view.getCarrier2View(); mCarrierGroups[2] = view.getCarrier3View(); @@ -247,10 +246,10 @@ public class QSCarrierGroupController { if (mNetworkController.hasVoiceCallingFeature()) { mNetworkController.addCallback(mSignalCallback); } - mCarrierTextController.setListening(mCallback); + mCarrierTextManager.setListening(mCallback); } else { mNetworkController.removeCallback(mSignalCallback); - mCarrierTextController.setListening(null); + mCarrierTextManager.setListening(null); } } @@ -277,7 +276,7 @@ public class QSCarrierGroupController { } @MainThread - private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { if (!mMainHandler.getLooper().isCurrentThread()) { mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); return; @@ -331,13 +330,13 @@ public class QSCarrierGroupController { } private static class H extends Handler { - private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo; + private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo; private Runnable mUpdateState; static final int MSG_UPDATE_CARRIER_INFO = 0; static final int MSG_UPDATE_STATE = 1; H(Looper looper, - Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo, + Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo, Runnable updateState) { super(looper); mUpdateCarrierInfo = updateCarrierInfo; @@ -349,7 +348,7 @@ public class QSCarrierGroupController { switch (msg.what) { case MSG_UPDATE_CARRIER_INFO: mUpdateCarrierInfo.accept( - (CarrierTextController.CarrierTextCallbackInfo) msg.obj); + (CarrierTextManager.CarrierTextCallbackInfo) msg.obj); break; case MSG_UPDATE_STATE: mUpdateState.run(); @@ -366,13 +365,13 @@ public class QSCarrierGroupController { private final Handler mHandler; private final Looper mLooper; private final NetworkController mNetworkController; - private final CarrierTextController.Builder mCarrierTextControllerBuilder; + private final CarrierTextManager.Builder mCarrierTextControllerBuilder; private final Context mContext; @Inject public Builder(ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder, Context context) { + CarrierTextManager.Builder carrierTextControllerBuilder, Context context) { mActivityStarter = activityStarter; mHandler = handler; mLooper = looper; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 6176a5702dcf..41445917a011 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -25,6 +25,7 @@ import com.android.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 @@ -91,7 +92,7 @@ class DeviceControlsTile @Inject constructor( override fun isAvailable(): Boolean { return featureFlags.isKeyguardLayoutEnabled && controlsLockscreen && - controlsComponent.getControlsUiController().isPresent + controlsComponent.getVisibility() != UNAVAILABLE } override fun newTileState(): QSTile.State { @@ -154,4 +155,4 @@ class DeviceControlsTile @Inject constructor( override fun getTileLabel(): CharSequence { return mContext.getText(R.string.quick_controls_title) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java index 26adfdcd8539..a6cddd3367d3 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 @@ public class UserTile extends QSTileImpl<State> implements UserInfoController.On @Override public DetailAdapter getDetailAdapter() { - return mUserSwitcherController.userDetailAdapter; + return mUserSwitcherController.mUserDetailAdapter; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index d56c806554d4..dc639dce4951 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -95,6 +95,17 @@ public class ScrollCaptureClient { this.requested = request; this.captured = captured; } + + @Override + public String toString() { + return "CaptureResult{" + + "requested=" + requested + + " (" + requested.width() + "x" + requested.height() + ")" + + ", captured=" + captured + + " (" + captured.width() + "x" + captured.height() + ")" + + ", image=" + image + + '}'; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index d97f644c5d23..ad5e637b189e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -253,7 +253,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener && result.captured.height() < result.requested.height(); boolean finish = false; - if (partialResult) { + if (partialResult || emptyResult) { // Potentially reached a vertical boundary. Extend in the other direction. switch (mDirection) { case DOWN: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 7aa41e43be3c..862c27907e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -67,6 +67,10 @@ public class FeatureFlags { return mFlagReader.isEnabled(R.bool.flag_brightness_slider); } + public boolean useNewLockscreenAnimations() { + return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations); + } + public boolean isPeopleTileEnabled() { return mFlagReader.isEnabled(R.bool.flag_conversations); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 2f0f90d318eb..c1feacaba440 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -11,14 +11,10 @@ import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffXfermode import android.graphics.RadialGradient import android.graphics.Shader -import android.os.SystemProperties import android.util.AttributeSet import android.view.View import com.android.systemui.Interpolators -val enableLightReveal = - SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false) - /** * Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to * 100% of the view(s) underneath the scrim. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 8c2fa3349e4a..85d8df8e6057 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -29,6 +29,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; @@ -54,6 +55,7 @@ public class DozeParameters implements TunerService.Tunable, private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; private final Resources mResources; private final BatteryController mBatteryController; + private final FeatureFlags mFeatureFlags; private boolean mDozeAlwaysOn; private boolean mControlScreenOffAnimation; @@ -65,7 +67,8 @@ public class DozeParameters implements TunerService.Tunable, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, PowerManager powerManager, BatteryController batteryController, - TunerService tunerService) { + TunerService tunerService, + FeatureFlags featureFlags) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -74,6 +77,7 @@ public class DozeParameters implements TunerService.Tunable, mControlScreenOffAnimation = !getDisplayNeedsBlanking(); mPowerManager = powerManager; mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); + mFeatureFlags = featureFlags; tunerService.addTunable( this, @@ -200,8 +204,7 @@ public class DozeParameters implements TunerService.Tunable, * then abruptly showing AOD. */ public boolean shouldControlUnlockedScreenOff() { - return getAlwaysOn() && SystemProperties.getBoolean( - "persist.sysui.show_new_screen_on_transitions", false); + return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations(); } private boolean getBoolean(String propName, int resId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 2ce0a8776266..986333ce5010 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 @@ package com.android.systemui.statusbar.phone; 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsComponent mControlsComponent; private int mLockScreenMode; private BroadcastDispatcher mBroadcastDispatcher; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -295,7 +297,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mRightExtension.destroy(); mLeftExtension.destroy(); getContext().unregisterReceiver(mDevicePolicyReceiver); - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); } private void initAccessibility() { @@ -410,12 +413,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); updateLeftAffordanceIcon(); + updateControlsVisibility(); if (dozing) { mOverlayContainer.setVisibility(INVISIBLE); @@ -889,35 +895,27 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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 e0df4f8bfea9..a6daed5a0850 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -56,10 +56,12 @@ public class KeyguardClockPositionAlgorithm { private int mKeyguardStatusHeight; /** - * Height of {@link KeyguardUserSwitcherListView} when it - * is closed and only the current user's icon is visible. + * Height of user avatar used by the multi-user switcher. This could either be the + * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is + * visible, or it could be height of the avatar used by the + * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}. */ - private int mKeyguardUserSwitcherHeight; + private int mUserSwitchHeight; /** * Preferred Y position of clock. @@ -67,6 +69,11 @@ public class KeyguardClockPositionAlgorithm { private int mClockPreferredY; /** + * Preferred Y position of user avatar used by the multi-user switcher. + */ + private int mUserSwitchPreferredY; + + /** * Whether or not there is a custom clock face on keyguard. */ private boolean mHasCustomClock; @@ -181,20 +188,21 @@ public class KeyguardClockPositionAlgorithm { */ public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight, float panelExpansion, int parentHeight, int keyguardStatusHeight, - int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock, - boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled, - int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion, - int cutoutTopInset) { + int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY, + boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, + boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon, + float qsExpansion, int cutoutTopInset) { mMinTopMargin = statusBarMinHeight + (showLockIcon ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon) - + keyguardUserSwitcherHeight; + + userSwitchHeight; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; mPanelExpansion = panelExpansion; mHeight = parentHeight; mKeyguardStatusHeight = keyguardStatusHeight; - mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight; + mUserSwitchHeight = userSwitchHeight; mClockPreferredY = clockPreferredY; + mUserSwitchPreferredY = userSwitchPreferredY; mHasCustomClock = hasCustomClock; mHasVisibleNotifs = hasVisibleNotifs; mDarkAmount = dark; @@ -208,6 +216,7 @@ public class KeyguardClockPositionAlgorithm { public void run(Result result) { final int y = getClockY(mPanelExpansion, mDarkAmount); result.clockY = y; + result.userSwitchY = getUserSwitcherY(mPanelExpansion); result.clockYFullyDozing = getClockY( 1.0f /* panelExpansion */, 1.0f /* darkAmount */); result.clockAlpha = getClockAlpha(y); @@ -241,7 +250,7 @@ public class KeyguardClockPositionAlgorithm { private int getExpandedPreferredClockY() { if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { - return mMinTopMargin; + return mMinTopMargin + mUserSwitchHeight; } return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY() : getExpandedClockPosition(); @@ -257,7 +266,7 @@ public class KeyguardClockPositionAlgorithm { final int containerCenter = mMinTopMargin + availableHeight / 2; float y = containerCenter - - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT + - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT - mClockNotificationsMargin - mNotificationStackHeight / 2; if (y < mMinTopMargin) { y = mMinTopMargin; @@ -299,6 +308,17 @@ public class KeyguardClockPositionAlgorithm { return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount); } + private int getUserSwitcherY(float panelExpansion) { + float userSwitchYRegular = mUserSwitchPreferredY; + float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight; + + // Move user-switch up while collapsing the shade + float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); + float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion); + + return (int) (userSwitchY + mEmptyDragAmount); + } + /** * We might want to fade out the clock when the user is swiping up. * One exception is when the bouncer will become visible, in this cause the clock @@ -341,6 +361,11 @@ public class KeyguardClockPositionAlgorithm { public int clockY; /** + * The y translation of the multi-user switch. + */ + public int userSwitchY; + + /** * The y translation of the clock when we're fully dozing. */ public int clockYFullyDozing; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java new file mode 100644 index 000000000000..377fb92ac6ba --- /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 d9cb9ce21330..16f36b7b6b7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java @@ -168,6 +168,6 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener } 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 3b09eda4003e..8aadef817eb0 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 @@ package com.android.systemui.statusbar.phone; import static android.view.View.GONE; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; +import static androidx.constraintlayout.widget.ConstraintSet.START; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; @@ -66,6 +70,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.constraintlayout.widget.ConstraintSet; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; @@ -77,6 +83,8 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; @@ -95,9 +103,11 @@ import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; @@ -133,6 +143,7 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; @@ -292,14 +303,13 @@ public class NotificationPanelViewController extends PanelViewController { new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() { @Override public void onKeyguardUserSwitcherChanged(boolean open) { - if (mKeyguardUserSwitcherController != null - && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { - return; + if (mKeyguardUserSwitcherController == null) { + updateUserSwitcherVisibility(false); + } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { + updateUserSwitcherVisibility(open + && mKeyguardStateController.isShowing() + && !mKeyguardStateController.isKeyguardFadingAway()); } - - updateUserSwitcherVisibility(open - && mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardFadingAway()); } }; @@ -315,7 +325,10 @@ public class NotificationPanelViewController extends PanelViewController { private final MediaHierarchyManager mMediaHierarchyManager; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; + private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; + private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; private final ControlsComponent mControlsComponent; @@ -327,8 +340,11 @@ public class NotificationPanelViewController extends PanelViewController { private int mMaxAllowedKeyguardNotifications; private KeyguardAffordanceHelper mAffordanceHelper; + private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; + private boolean mKeyguardUserSwitcherIsShowing; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; + private KeyguardStatusBarViewController mKeyguarStatusBarViewController; private ViewGroup mBigClockContainer; private QS mQs; private FrameLayout mQsFrame; @@ -352,6 +368,7 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mQsExpandedWhenExpandingStarted; private boolean mQsFullyExpanded; private boolean mKeyguardShowing; + private boolean mKeyguardQsUserSwitchEnabled; private boolean mKeyguardUserSwitcherEnabled; private boolean mDozing; private boolean mDozingOnDown; @@ -578,7 +595,10 @@ public class NotificationPanelViewController extends PanelViewController { StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, + KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, + KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, + QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, AuthController authController, @@ -604,10 +624,16 @@ public class NotificationPanelViewController extends PanelViewController { mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; + mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory; mFeatureFlags = featureFlags; + mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory; mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory; + mQSDetailDisplayer = qsDetailDisplayer; mKeyguardUserSwitcherEnabled = mResources.getBoolean( com.android.internal.R.bool.config_keyguardUserSwitcher); + mKeyguardQsUserSwitchEnabled = + mKeyguardUserSwitcherEnabled && mResources.getBoolean( + R.bool.config_keyguard_user_switch_opens_qs_details); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -688,14 +714,22 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); mBigClockContainer = mView.findViewById(R.id.big_clock_container); + UserAvatarView userAvatarView = null; KeyguardUserSwitcherView keyguardUserSwitcherView = null; if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) { - ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub); - keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate(); + if (mKeyguardQsUserSwitchEnabled) { + ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub); + userAvatarView = (UserAvatarView) stub.inflate(); + } else { + ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub); + keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate(); + } } updateViewControllers(mView.findViewById(R.id.keyguard_status_view), + userAvatarView, + mKeyguardStatusBar, keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( @@ -741,6 +775,10 @@ public class NotificationPanelViewController extends PanelViewController { }); mView.setAccessibilityDelegate(mAccessibilityDelegate); + // dynamically apply the split shade value overrides. + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + updateResources(); + } } @Override @@ -770,6 +808,8 @@ public class NotificationPanelViewController extends PanelViewController { } private void updateViewControllers(KeyguardStatusView keyguardStatusView, + UserAvatarView userAvatarView, + KeyguardStatusBarView keyguardStatusBarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = @@ -777,6 +817,12 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + KeyguardStatusBarViewComponent statusBarViewComponent = + mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView); + mKeyguarStatusBarViewController = + statusBarViewComponent.getKeyguardStatusBarViewController(); + mKeyguarStatusBarViewController.init(); + // Re-associate the clock container with the keyguard clock switch. KeyguardClockSwitchController keyguardClockSwitchController = statusViewComponent.getKeyguardClockSwitchController(); @@ -789,18 +835,27 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardUserSwitcherController.removeCallback(); } + mKeyguardQsUserSwitchController = null; + mKeyguardUserSwitcherController = null; + // Re-associate the KeyguardUserSwitcherController - if (keyguardUserSwitcherView != null) { + if (userAvatarView != null) { + KeyguardQsUserSwitchComponent userSwitcherComponent = + mKeyguardQsUserSwitchComponentFactory.build(userAvatarView); + mKeyguardQsUserSwitchController = + userSwitcherComponent.getKeyguardQsUserSwitchController(); + mKeyguardQsUserSwitchController.setNotificationPanelViewController(this); + mKeyguardQsUserSwitchController.init(); + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); + } else if (keyguardUserSwitcherView != null) { KeyguardUserSwitcherComponent userSwitcherComponent = mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); - mKeyguardUserSwitcherController = userSwitcherComponent.getKeyguardUserSwitcherController(); mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener); mKeyguardUserSwitcherController.init(); mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); } else { - mKeyguardUserSwitcherController = null; mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false); } } @@ -840,11 +895,21 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setLayoutParams(lp); } + // In order to change the constraints at runtime, all children of the Constraint Layout + // must have ids. + ensureAllViewsHaveIds(mNotificationContainerParent); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { - // In order to change the constraints at runtime, all children of the Constraint Layout - // must have ids. - ensureAllViewsHaveIds(mNotificationContainerParent); + constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END); + constraintSet.connect( + R.id.notification_stack_scroller, START, + R.id.qs_edge_guideline, START); + } else { + constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END); + constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START); } + constraintSet.applyTo(mNotificationContainerParent); } private static void ensureAllViewsHaveIds(ViewGroup parentView) { @@ -856,6 +921,25 @@ public class NotificationPanelViewController extends PanelViewController { } } + private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) { + View view = mView.findViewById(viewId); + if (view != null) { + int index = mView.indexOfChild(view); + mView.removeView(view); + if (enabled) { + view = mLayoutInflater.inflate(layoutId, mView, false); + mView.addView(view, index); + } else { + view = null; + } + } else if (enabled) { + // It's possible the stub was never inflated if the configuration changed + ViewStub stub = mView.findViewById(stubId); + view = stub.inflate(); + } + return view; + } + private void reInflateViews() { if (DEBUG) Log.d(TAG, "reInflateViews"); // Re-inflate the status view group. @@ -867,26 +951,27 @@ public class NotificationPanelViewController extends PanelViewController { mView.addView(keyguardStatusView, index); // Re-inflate the keyguard user switcher group. - boolean showUserSwitcher = - mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled(); - KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById( - R.id.keyguard_user_switcher_view); - if (keyguardUserSwitcherView != null) { - index = mView.indexOfChild(keyguardUserSwitcherView); - mView.removeView(keyguardUserSwitcherView); - if (showUserSwitcher) { - keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate( - R.layout.keyguard_user_switcher, mView, false); - mView.addView(keyguardUserSwitcherView, index); - } - } else if (showUserSwitcher) { - // It's possible the user switcher was never inflated if the configuration changed - ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub); - keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate(); - } + boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled(); + boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled; + boolean showKeyguardUserSwitcher = + !mKeyguardQsUserSwitchEnabled + && mKeyguardUserSwitcherEnabled + && isUserSwitcherEnabled; + UserAvatarView userAvatarView = (UserAvatarView) reInflateStub( + R.id.keyguard_qs_user_switch_view /* viewId */, + R.id.keyguard_qs_user_switch_stub /* stubId */, + R.layout.keyguard_qs_user_switch /* layoutId */, + showQsUserSwitch /* enabled */); + KeyguardUserSwitcherView keyguardUserSwitcherView = + (KeyguardUserSwitcherView) reInflateStub( + R.id.keyguard_user_switcher_view /* viewId */, + R.id.keyguard_user_switcher_stub /* stubId */, + R.layout.keyguard_user_switcher /* layoutId */, + showKeyguardUserSwitcher /* enabled */); mBigClockContainer.removeAllViews(); - updateViewControllers(keyguardStatusView, keyguardUserSwitcherView); + updateViewControllers( + keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView); // Update keyguard bottom area index = mView.indexOfChild(mKeyguardBottomArea); @@ -910,6 +995,13 @@ public class NotificationPanelViewController extends PanelViewController { false, false, mBarState); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( + mBarState, + false, + false, + mBarState); + } if (mKeyguardUserSwitcherController != null) { mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( mBarState, @@ -1009,10 +1101,15 @@ public class NotificationPanelViewController extends PanelViewController { int totalHeight = mView.getHeight(); int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); + int userSwitcherPreferredY = mStatusBarMinHeight; boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications); + int userIconHeight = mKeyguardQsUserSwitchController != null + ? mKeyguardQsUserSwitchController.getUserIconHeight() + : (mKeyguardUserSwitcherController != null + ? mKeyguardUserSwitcherController.getUserIconHeight() : 0); mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), getExpandedFraction(), @@ -1021,9 +1118,8 @@ public class NotificationPanelViewController extends PanelViewController { ? mKeyguardStatusViewController.getHeight() : (int) (mKeyguardStatusViewController.getHeight() - mShelfHeight / 2.0f - mDarkIconSize / 2.0f), - mKeyguardUserSwitcherController == null - ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(), - clockPreferredY, hasCustomClock(), + userIconHeight, + clockPreferredY, userSwitcherPreferredY, hasCustomClock(), hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount, bypassEnabled, getUnlockedStackScrollerPadding(), mUpdateMonitor.canShowLockIcon(), @@ -1033,11 +1129,16 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.userSwitchY, + animateClock); + } if (mKeyguardUserSwitcherController != null) { mKeyguardUserSwitcherController.updatePosition( mClockPositionResult.clockX, - mClockPositionResult.clockY - - mKeyguardUserSwitcherController.getUserIconHeight(), + mClockPositionResult.userSwitchY, animateClock); } updateNotificationTranslucency(); @@ -1180,6 +1281,9 @@ public class NotificationPanelViewController extends PanelViewController { private void updateClock() { mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha); + } if (mKeyguardUserSwitcherController != null) { mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha); } @@ -1271,6 +1375,12 @@ public class NotificationPanelViewController extends PanelViewController { } } + public void expandWithQsDetail(DetailAdapter qsDetailAdapter) { + traceQsJank(true /* startTracing */, false /* wasCancelled */); + flingSettings(0 /* velocity */, FLING_EXPAND); + mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0); + } + public void expandWithoutQs() { if (isQsExpanded()) { flingSettings(0 /* velocity */, FLING_COLLAPSE); @@ -1933,6 +2043,10 @@ public class NotificationPanelViewController extends PanelViewController { } private float calculateQsTopPadding() { + // in split shade mode we want notifications to be directly below status bar + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) { + return 0f; + } if (mKeyguardShowing && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { @@ -2572,7 +2686,9 @@ public class NotificationPanelViewController extends PanelViewController { super.onTrackingStarted(); if (mQsFullyExpanded) { mQsExpandImmediate = true; - mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true); + if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true); + } } if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) { mAffordanceHelper.animateHideLeftRightIcon(); @@ -2814,12 +2930,12 @@ public class NotificationPanelViewController extends PanelViewController { } /** - * Updates the vertical position of the panel so it is positioned closer to the touch + * Updates the horizontal position of the panel so it is positioned closer to the touch * responsible for opening the panel. * * @param x the x-coordinate the touch event */ - protected void updateVerticalPanelPosition(float x) { + protected void updateHorizontalPanelPosition(float x) { if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) { resetHorizontalPanelPosition(); return; @@ -3166,6 +3282,13 @@ public class NotificationPanelViewController extends PanelViewController { true /* keyguardFadingAway */, false /* goingToFullShade */, mBarState); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( + mBarState, + true /* keyguardFadingAway */, + false /* goingToFullShade */, + mBarState); + } if (mKeyguardUserSwitcherController != null) { mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( mBarState, @@ -3396,7 +3519,7 @@ public class NotificationPanelViewController extends PanelViewController { } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - updateVerticalPanelPosition(event.getX()); + updateHorizontalPanelPosition(event.getX()); handled = true; } handled |= super.onTouch(v, event); @@ -3432,6 +3555,12 @@ public class NotificationPanelViewController extends PanelViewController { } private void updateUserSwitcherVisibility(boolean open) { + // Do not update if previously called with the same state. + if (mKeyguardUserSwitcherIsShowing == open) { + return; + } + mKeyguardUserSwitcherIsShowing = open; + if (open) { animateKeyguardStatusBarOut(); mKeyguardStatusViewController.setKeyguardStatusViewVisibility( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e63902f26a78..041a97e1d404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -35,7 +35,6 @@ import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTE import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; -import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; @@ -180,6 +179,7 @@ import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -440,6 +440,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardViewMediator mKeyguardViewMediator; protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSlider.Factory mBrightnessSliderFactory; + private final FeatureFlags mFeatureFlags; private final List<ExpansionChangedListener> mExpansionChangedListeners; @@ -762,7 +763,8 @@ public class StatusBar extends SystemUI implements DemoMode, Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory) { + BrightnessSlider.Factory brightnessSliderFactory, + FeatureFlags featureFlags) { super(context); mNotificationsController = notificationsController; mLightBarController = lightBarController; @@ -840,6 +842,7 @@ public class StatusBar extends SystemUI implements DemoMode, mDemoModeController = demoModeController; mNotificationIconAreaController = notificationIconAreaController; mBrightnessSliderFactory = brightnessSliderFactory; + mFeatureFlags = featureFlags; mExpansionChangedListeners = new ArrayList<>(); @@ -1181,9 +1184,11 @@ public class StatusBar extends SystemUI implements DemoMode, mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - if (getEnableLightReveal()) { + if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) { mLightRevealScrim.setVisibility(View.VISIBLE); mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); + } else { + mLightRevealScrim.setVisibility(View.GONE); } mNotificationPanelViewController.initDependencies( @@ -3614,7 +3619,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { - if (getEnableLightReveal()) { + if (mFeatureFlags.useNewLockscreenAnimations()) { mLightRevealScrim.setRevealAmount(1f - linear); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 9e9533d0e199..b572c57590ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -47,6 +47,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -200,7 +201,8 @@ public interface StatusBarPhoneModule { DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory) { + BrightnessSlider.Factory brightnessSliderFactory, + FeatureFlags featureFlags) { return new StatusBar( context, notificationsController, @@ -279,6 +281,7 @@ public interface StatusBarPhoneModule { notificationShadeDepthController, statusBarTouchableRegionManager, notificationIconAreaController, - brightnessSliderFactory); + brightnessSliderFactory, + featureFlags); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java new file mode 100644 index 000000000000..38f3bc891394 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardConstants; +import com.android.keyguard.KeyguardVisibilityHelper; +import com.android.keyguard.dagger.KeyguardUserSwitcherScope; +import com.android.settingslib.drawable.CircleFramedDrawable; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; +import com.android.systemui.statusbar.phone.UserAvatarView; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; + +/** + * Manages the user switch on the Keyguard that is used for opening the QS user panel. + */ +@KeyguardUserSwitcherScope +public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> { + + private static final String TAG = "KeyguardQsUserSwitchController"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + private static final AnimationProperties ANIMATION_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + + private final Context mContext; + private Resources mResources; + private final UserSwitcherController mUserSwitcherController; + private final ScreenLifecycle mScreenLifecycle; + private UserSwitcherController.BaseUserAdapter mAdapter; + private final KeyguardStateController mKeyguardStateController; + protected final SysuiStatusBarStateController mStatusBarStateController; + private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private final KeyguardUserDetailAdapter mUserDetailAdapter; + private NotificationPanelViewController mNotificationPanelViewController; + private UserManager mUserManager; + UserSwitcherController.UserRecord mCurrentUser; + + // State info for the user switch and keyguard + private int mBarState; + private float mDarkAmount; + + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState)); + + boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); + boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); + int oldState = mBarState; + mBarState = newState; + + setKeyguardQsUserSwitchVisibility( + newState, + keyguardFadingAway, + goingToFullShade, + oldState); + } + + @Override + public void onDozeAmountChanged(float linearAmount, float amount) { + if (DEBUG) { + Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", + linearAmount, amount)); + } + setDarkAmount(amount); + } + }; + + @Inject + public KeyguardQsUserSwitchController( + UserAvatarView view, + Context context, + @Main Resources resources, + UserManager userManager, + ScreenLifecycle screenLifecycle, + UserSwitcherController userSwitcherController, + KeyguardStateController keyguardStateController, + SysuiStatusBarStateController statusBarStateController, + DozeParameters dozeParameters, + UiEventLogger uiEventLogger) { + super(view); + if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); + mContext = context; + mResources = resources; + mUserManager = userManager; + mScreenLifecycle = screenLifecycle; + mUserSwitcherController = userSwitcherController; + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = statusBarStateController; + mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, + keyguardStateController, dozeParameters); + mUserDetailAdapter = new KeyguardUserDetailAdapter(mUserSwitcherController, mContext, + uiEventLogger); + } + + @Override + protected void onInit() { + super.onInit(); + if (DEBUG) Log.d(TAG, "onInit"); + mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return null; + } + }; + + mView.setOnClickListener(v -> { + if (isListAnimating()) { + return; + } + + // Tapping anywhere in the view will open QS user panel + openQsUserPanel(); + }); + + updateView(true /* forceUpdate */); + } + + @Override + protected void onViewAttached() { + if (DEBUG) Log.d(TAG, "onViewAttached"); + mAdapter.registerDataSetObserver(mDataSetObserver); + mDataSetObserver.onChanged(); + mStatusBarStateController.addCallback(mStatusBarStateListener); + } + + @Override + protected void onViewDetached() { + if (DEBUG) Log.d(TAG, "onViewDetached"); + + mAdapter.unregisterDataSetObserver(mDataSetObserver); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + } + + public final DataSetObserver mDataSetObserver = new DataSetObserver() { + @Override + public void onChanged() { + updateView(false /* forceUpdate */); + } + }; + + /** + * @return true if the current user has changed + */ + private boolean updateCurrentUser() { + UserSwitcherController.UserRecord previousUser = mCurrentUser; + mCurrentUser = null; + for (int i = 0; i < mAdapter.getCount(); i++) { + UserSwitcherController.UserRecord r = mAdapter.getItem(i); + if (r.isCurrent) { + mCurrentUser = r; + return !mCurrentUser.equals(previousUser); + } + } + return mCurrentUser == null && previousUser != null; + } + + /** + * @param forceUpdate whether to update view even if current user did not change + */ + private void updateView(boolean forceUpdate) { + if (!updateCurrentUser() && !forceUpdate) { + return; + } + + if (mCurrentUser == null) { + mView.setVisibility(View.GONE); + return; + } + + mView.setVisibility(View.VISIBLE); + + String currentUserName = mCurrentUser.info.name; + String contentDescription = null; + + if (!TextUtils.isEmpty(currentUserName)) { + contentDescription = mContext.getString( + R.string.accessibility_quick_settings_user, + currentUserName); + } + + if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) { + mView.setContentDescription(contentDescription); + } + + if (mCurrentUser.picture == null) { + mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(), + mCurrentUser.resolveId()); + } else { + int avatarSize = + (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); + Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); + drawable.setColorFilter( + mCurrentUser.isSwitchToEnabled ? null + : mAdapter.getDisabledUserAvatarColorFilter()); + mView.setDrawableWithBadge(drawable, mCurrentUser.info.id); + } + } + + Drawable getDrawable(UserSwitcherController.UserRecord item) { + Drawable drawable; + if (item.isCurrent && item.isGuest) { + drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); + } else { + drawable = mAdapter.getIconDrawable(mContext, item); + } + + int iconColorRes; + if (item.isSwitchToEnabled) { + iconColorRes = R.color.kg_user_switcher_avatar_icon_color; + } else { + iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color; + } + drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); + + Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); + drawable = new LayerDrawable(new Drawable[]{bg, drawable}); + return drawable; + } + + /** + * Get the height of the keyguard user switcher view when closed. + */ + public int getUserIconHeight() { + return mView.getHeight(); + } + + /** + * Set the visibility of the user avatar view based on some new state. + */ + public void setKeyguardQsUserSwitchVisibility( + int statusBarState, + boolean keyguardFadingAway, + boolean goingToFullShade, + int oldStatusBarState) { + mKeyguardVisibilityHelper.setViewVisibility( + statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); + } + + /** + * Update position of the view with an optional animation + */ + public void updatePosition(int x, int y, boolean animate) { + PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate); + PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), + ANIMATION_PROPERTIES, animate); + } + + /** + * Set keyguard user avatar view alpha. + */ + public void setAlpha(float alpha) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + mView.setAlpha(alpha); + } + } + + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ + private void setDarkAmount(float darkAmount) { + boolean isAwake = darkAmount != 0; + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); + } + + private boolean isListAnimating() { + return mKeyguardVisibilityHelper.isVisibilityAnimating(); + } + + private void openQsUserPanel() { + mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter); + } + + public void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController) { + mNotificationPanelViewController = notificationPanelViewController; + } + + class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter { + KeyguardUserDetailAdapter(UserSwitcherController userSwitcherController, Context context, + UiEventLogger uiEventLogger) { + super(userSwitcherController, context, uiEventLogger); + } + + @Override + public boolean shouldAnimate() { + return false; + } + + @Override + public boolean onDoneButtonClicked() { + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.animateCloseQs(true /* animateAway */); + return true; + } else { + return false; + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 019ab479e28e..d4029e64036e 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 @@ public class UserSwitcherController implements Dumpable { 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 @@ public class UserSwitcherController implements Dumpable { mBroadcastDispatcher = broadcastDispatcher; mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; + mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger); if (!UserManager.isGuestUserEphemeral()) { mGuestResumeSessionReceiver.register(mBroadcastDispatcher); } @@ -781,9 +783,20 @@ public class UserSwitcherController implements Dumpable { } } - public final DetailAdapter userDetailAdapter = new DetailAdapter() { + public static class UserDetailAdapter implements DetailAdapter { private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS); + private final UserSwitcherController mUserSwitcherController; + private final Context mContext; + private final UiEventLogger mUiEventLogger; + + UserDetailAdapter(UserSwitcherController userSwitcherController, Context context, + UiEventLogger uiEventLogger) { + mUserSwitcherController = userSwitcherController; + mContext = context; + mUiEventLogger = uiEventLogger; + } + @Override public CharSequence getTitle() { return mContext.getString(R.string.quick_settings_user_title); @@ -794,7 +807,7 @@ public class UserSwitcherController implements Dumpable { UserDetailView v; if (!(convertView instanceof UserDetailView)) { v = UserDetailView.inflate(context, parent, false); - v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger); + v.createAndSetAdapter(mUserSwitcherController, mUiEventLogger); } else { v = (UserDetailView) convertView; } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index ec61db591324..8505703b9e25 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -366,7 +366,7 @@ public final class WMShell extends SystemUI 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 449db61a0fbb..fba0b0079012 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 static android.os.Process.THREAD_PRIORITY_DISPLAY; import android.animation.AnimationHandler; import android.app.ActivityTaskManager; -import android.app.IActivityManager; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; @@ -73,7 +72,6 @@ import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipAppOpsListener; -import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; @@ -211,8 +209,8 @@ public abstract class WMShellBaseModule { @Provides static SizeCompatUIController provideSizeCompatUIController(Context context, DisplayController displayController, DisplayImeController imeController, - @ShellMainThread ShellExecutor mainExecutor) { - return new SizeCompatUIController(context, displayController, imeController, mainExecutor); + SyncTransactionQueue syncQueue) { + return new SizeCompatUIController(context, displayController, imeController, syncQueue); } @WMSingleton diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index aa4122fd190a..d3f9d641ca9f 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.AndroidTestingRunner; 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 @@ import java.util.List; @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 @@ public class CarrierTextControllerTest extends SysuiTestCase { @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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 c2ade81a9877..d67fe6dfb0b2 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 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { private KeyguardMessageAreaController mKeyguardMessageAreaController; @Mock private LatencyTracker mLatencyTracker; + @Mock + private EmergencyButtonController mEmergencyButtonController; private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; @@ -84,7 +86,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { .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 826be2ba0d83..4beec574cd2a 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 @@ import java.util.concurrent.Executor; 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 class KeyguardDisplayManagerTest extends SysuiTestCase { 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 c69ec1a254c3..6d0c64088abc 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 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { @Mock private lateinit var mLatencyTracker: LatencyTracker @Mock + private lateinit var mEmergencyButtonController: EmergencyButtonController + @Mock private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory @Mock @@ -72,7 +74,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { .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 31cc7bb7c958..8d1e1a4a4463 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 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { private LatencyTracker mLatencyTracker; @Mock private LiftToActivateListener mLiftToactivateListener; + @Mock + private EmergencyButtonController mEmergencyButtonController; private FalsingCollector mFalsingCollector = new FalsingCollectorFake(); @Mock private View mDeleteButton; @@ -92,7 +94,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { 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 3b7f4b839853..9296d3d5ec82 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 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { @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 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { 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 0c69ffdef372..10332bd31bd1 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 @@ public class ImageWallpaperTest extends SysuiTestCase { return new ImageWallpaper() { @Override public Engine onCreateEngine() { + onCreate(); return new GLEngine(mHandler) { @Override public Context getDisplayContext() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt index 7fe682793152..b8f91b8d4719 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.Assert.assertTrue 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 @@ class ControlsComponentTest : SysuiTestCase() { 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 @@ class ControlsComponentTest : SysuiTestCase() { @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 069699c271f7..6d8c372a061b 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 @@ public class DozeUiTest extends SysuiTestCase { mDozeUi.transitionTo(UNINITIALIZED, DOZE); verify(mHost).setAnimateWakeup(eq(false)); } + + @Test + public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() { + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + + // Tell doze that keyguard is not visible. + mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); + + // Since we're controlling the unlocked screen off animation, verify that we've asked to + // control the screen off animation despite being unlocked. + verify(mDozeParameters).setControlScreenOffAnimation(true); + } + + @Test + public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() { + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false); + + // Tell doze that keyguard is not visible. + mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); + + // Since we're not controlling the unlocked screen off animation, verify that we haven't + // asked to control the screen off animation since we're unlocked. + verify(mDozeParameters).setControlScreenOffAnimation(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 78ee5936fe0b..1062fae52e7a 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.ActivityStarter; 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { 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 00943bc53bfd..b8c37fde2ce3 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 @@ package com.android.systemui.keyguard; 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.never; 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.classifier.FalsingCollectorFake; 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.mViewMediatorCallback.keyguardGone(); verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false)); } + + @Test + public void testIsAnimatingScreenOff() { + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + + mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false); + mViewMediator.setDozing(true); + + // Mid-doze, we should be animating the screen off animation. + mViewMediator.onDozeAmountChanged(0.5f, 0.5f); + assertTrue(mViewMediator.isAnimatingScreenOff()); + + // Once we're 100% dozed, the screen off animation should be completed. + mViewMediator.onDozeAmountChanged(1f, 1f); + assertFalse(mViewMediator.isAnimatingScreenOff()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java new file mode 100644 index 000000000000..b3ad6ef8da6e --- /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 000000000000..ac1893413c50 --- /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/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java index 1ec1da44c0b5..e855834dcad4 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 android.widget.TextView; 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { private QSCarrierGroupController mQSCarrierGroupController; private NetworkController.SignalCallback mSignalCallback; - private CarrierTextController.CarrierTextCallback mCallback; + private CarrierTextManager.CarrierTextCallback mCallback; @Mock private QSCarrierGroup mQSCarrierGroup; @Mock @@ -62,9 +62,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { @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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { .thenReturn(mCarrierTextControllerBuilder); when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean())) .thenReturn(mCarrierTextControllerBuilder); - when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController); + when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager); doAnswer(invocation -> mCallback = invocation.getArgument(0)) - .when(mCarrierTextController) - .setListening(any(CarrierTextController.CarrierTextCallback.class)); + .when(mCarrierTextManager) + .setListening(any(CarrierTextManager.CarrierTextCallback.class)); when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext)); when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class)); @@ -113,8 +113,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { (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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { (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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { @Test public void testNoEmptyVisibleView_airplaneMode() { - CarrierTextController.CarrierTextCallbackInfo - info = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + info = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index b7b967866d47..d3dbe2bf7d2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -25,6 +25,7 @@ import androidx.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 @@ -36,15 +37,19 @@ 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 @@ -91,6 +96,15 @@ class DeviceControlsTileTest : SysuiTestCase() { private lateinit var testableLooper: TestableLooper private lateinit var tile: DeviceControlsTile + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var userTracker: UserTracker + @Mock + private lateinit var lockPatternUtils: LockPatternUtils + @Mock + private lateinit var secureSettings: SecureSettings + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -101,9 +115,14 @@ class DeviceControlsTileTest : SysuiTestCase() { controlsComponent = ControlsComponent( true, + mContext, { controlsController }, { controlsUiController }, - { controlsListingController } + { controlsListingController }, + lockPatternUtils, + keyguardStateController, + userTracker, + secureSettings ) globalSettings = FakeSettings() @@ -111,16 +130,20 @@ class DeviceControlsTileTest : SysuiTestCase() { globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1) `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true) + `when`(userTracker.userHandle.identifier).thenReturn(0) + tile = createTile() } @Test fun testAvailable() { + `when`(controlsController.available).thenReturn(true) assertThat(tile.isAvailable).isTrue() } @Test fun testNotAvailableFeature() { + `when`(controlsController.available).thenReturn(true) `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false) assertThat(tile.isAvailable).isFalse() @@ -130,9 +153,14 @@ class DeviceControlsTileTest : SysuiTestCase() { fun testNotAvailableControls() { controlsComponent = ControlsComponent( false, + mContext, { controlsController }, { controlsUiController }, - { controlsListingController } + { controlsListingController }, + lockPatternUtils, + keyguardStateController, + userTracker, + secureSettings ) tile = createTile() @@ -264,4 +292,4 @@ class DeviceControlsTileTest : SysuiTestCase() { globalSettings ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index fa253e62ef0a..421c6f4aab0b 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 @@ package com.android.systemui.statusbar.phone; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; @@ -35,6 +37,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; @@ -57,6 +60,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private PowerManager mPowerManager; @Mock private TunerService mTunerService; @Mock private BatteryController mBatteryController; + @Mock private FeatureFlags mFeatureFlags; @Before public void setup() { @@ -67,7 +71,8 @@ public class DozeParametersTest extends SysuiTestCase { mAlwaysOnDisplayPolicy, mPowerManager, mBatteryController, - mTunerService + mTunerService, + mFeatureFlags ); } @Test @@ -111,4 +116,37 @@ public class DozeParametersTest extends SysuiTestCase { 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 4162884680a9..9b623f950505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -394,8 +394,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private void positionClock() { mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight, mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, - 0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock, - mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */, + 0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */, + mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */, 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion, mCutoutTopInset); mClockPositionAlgorithm.run(mClockPosition); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index d0e70310810e..d69d2dbdd841 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -48,6 +48,8 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -58,6 +60,8 @@ import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.R; @@ -70,6 +74,7 @@ import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; +import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardAffordanceView; @@ -194,14 +199,24 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; @Mock - private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent; + private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; + @Mock + private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; + @Mock + private QSDetailDisplayer mQSDetailDisplayer; @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock + private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock + private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; + @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock private KeyguardStatusViewController mKeyguardStatusViewController; @Mock + private KeyguardStatusBarViewController mKeyguardStatusBarViewController; + @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private AuthController mAuthController; @@ -216,14 +231,13 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private NotificationsQuickSettingsContainer mNotificationContainerParent; - @Mock private AmbientState mAmbientState; @Mock private UserManager mUserManager; private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; + private NotificationsQuickSettingsContainer mNotificationContainerParent; @Before public void setup() { @@ -256,6 +270,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); + mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); when(mView.findViewById(R.id.notification_container_parent)) .thenReturn(mNotificationContainerParent); FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( @@ -289,6 +304,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { new ViewGroup.LayoutParams(600, 400)); when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn( new ViewGroup.LayoutParams(600, 400)); + when(mKeyguardStatusBarViewComponentFactory.build(any())) + .thenReturn(mKeyguardStatusBarViewComponent); + when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController()) + .thenReturn(mKeyguardStatusBarViewController); mNotificationPanelViewController = new NotificationPanelViewController(mView, mResources, @@ -305,7 +324,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { mBiometricUnlockController, mStatusBarKeyguardViewManager, mNotificationStackScrollLayoutController, mKeyguardStatusViewComponentFactory, - mKeyguardUserSwitcherComponent, + mKeyguardQsUserSwitchComponentFactory, + mKeyguardUserSwitcherComponentFactory, + mKeyguardStatusBarViewComponentFactory, + mQSDetailDisplayer, mGroupManager, mNotificationAreaController, mAuthController, @@ -432,16 +454,11 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Test public void testAllChildrenOfNotificationContainer_haveIds() { - when(mNotificationContainerParent.getChildCount()).thenReturn(2); when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); - View view1 = new View(mContext); - view1.setId(1); - when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1); - - View view2 = mock(View.class); - when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2); + mNotificationContainerParent.addView(newViewWithId(1)); + mNotificationContainerParent.addView(newViewWithId(View.NO_ID)); mNotificationPanelViewController.updateResources(); @@ -449,6 +466,51 @@ public class NotificationPanelViewTest extends SysuiTestCase { assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID); } + @Test + public void testSinglePaneShadeLayout_isAlignedToParent() { + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false); + mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame)); + mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller)); + + mNotificationPanelViewController.updateResources(); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); + ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout; + ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint( + R.id.notification_stack_scroller).layout; + assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID); + assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID); + } + + @Test + public void testSplitShadeLayout_isAlignedToGuideline() { + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); + mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame)); + mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller)); + + mNotificationPanelViewController.updateResources(); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); + ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout; + ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint( + R.id.notification_stack_scroller).layout; + assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline); + assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline); + } + + private View newViewWithId(int id) { + View view = new View(mContext); + view.setId(id); + ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + // required as cloning ConstraintSet fails if view doesn't have layout params + view.setLayoutParams(layoutParams); + return view; + } + private void onTouchEvent(MotionEvent ev) { mTouchHandler.onTouch(mView, ev); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index cae488a561a2..253460db0d07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -97,6 +97,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -258,6 +259,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DemoModeController mDemoModeController; @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; + @Mock private FeatureFlags mFeatureFlags; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -418,7 +420,8 @@ public class StatusBarTest extends SysuiTestCase { mNotificationShadeDepthControllerLazy, mStatusBarTouchableRegionManager, mNotificationIconAreaController, - mBrightnessSliderFactory); + mBrightnessSliderFactory, + mFeatureFlags); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( mLockIconContainer); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java index b36626f9d736..0d323fb186c9 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.Process; 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 @@ import java.io.PrintWriter; 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 @@ final class AccessibilityShellCommand extends ShellCommand { case "call-system-action": { return runCallSystemAction(); } + case "start-trace": + return startTrace(); + case "stop-trace": + return stopTrace(); } return -1; } @@ -98,6 +107,20 @@ final class AccessibilityShellCommand extends ShellCommand { 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 @@ final class AccessibilityShellCommand extends ShellCommand { 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 afa08405d642..9f369101dd7c 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -129,8 +129,8 @@ java_library_static { "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", @@ -228,8 +228,5 @@ filegroup { "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 1012875a41bd..154e1831ceee 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,7 +44,6 @@ 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_OEM_PAID; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; @@ -141,7 +139,6 @@ import android.net.UidRangeParcel; 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; @@ -173,8 +170,6 @@ import android.os.SystemProperties; 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; @@ -190,9 +185,6 @@ import com.android.internal.annotations.GuardedBy; 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; @@ -217,9 +209,7 @@ import com.android.server.connectivity.NetworkRanker; 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; @@ -312,18 +302,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ -764,6 +743,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // 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)) { @@ -781,9 +781,17 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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); } } @@ -978,13 +986,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - - /** * @see ProxyTracker */ public ProxyTracker makeProxyTracker(@NonNull Context context, @@ -1093,7 +1094,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); @@ -1182,43 +1182,15 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); @@ -1259,7 +1231,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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; @@ -1274,7 +1245,6 @@ public class ConnectivityService extends IConnectivityManager.Stub int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); if (transportType > TYPE_NONE) { netCap.addTransportType(transportType); @@ -1407,9 +1377,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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; @@ -1494,11 +1462,9 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 */); } /** @@ -1557,14 +1523,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); @@ -2186,22 +2144,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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) { @@ -2282,12 +2224,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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, @@ -2366,13 +2302,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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()); @@ -2466,10 +2395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // 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)); } @@ -2953,7 +2878,15 @@ public class ConnectivityService extends IConnectivityManager.Stub 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; @@ -3562,7 +3495,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // incorrect) behavior. mNetworkActivityTracker.updateDataActivityTracking( null /* newNetwork */, nai); - notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.toShortString()); } } @@ -3652,10 +3584,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { - handleRegisterNetworkRequest(Collections.singletonList(nri)); + handleRegisterNetworkRequests(Collections.singleton(nri)); } - private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) { + private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); for (final NetworkRequestInfo nri : nris) { mNetworkRequestInfoLogs.log("REGISTER " + nri); @@ -3786,7 +3718,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private NetworkRequestInfo getNriForAppRequest( NetworkRequest request, int callingUid, String requestedOperation) { - final NetworkRequestInfo nri = mNetworkRequests.get(request); + // Looking up the app passed param request in mRequests isn't possible since it may return + // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to + // do the lookup since that will also find per-app default managed requests. + final NetworkRequestInfo nri = getNriForAppRequest(request); if (nri != null) { if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid @@ -3835,8 +3770,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nri == null) { return; } - // handleReleaseNetworkRequest() paths don't apply to multilayer requests. - ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest"); if (VDBG || (DBG && request.isRequest())) { log("releasing " + request + " (release request)"); } @@ -3848,7 +3781,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) { ensureRunningOnConnectivityServiceThread(); - nri.unlinkDeathRecipient(); for (final NetworkRequest req : nri.mRequests) { mNetworkRequests.remove(req); @@ -3871,6 +3803,16 @@ public class ConnectivityService extends IConnectivityManager.Stub cancelNpiRequests(nri); } + private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { + for (final NetworkRequestInfo nri : nris) { + if (mDefaultRequest == nri) { + // Make sure we never remove the default request. + continue; + } + handleRemoveNetworkRequest(nri); + } + } + private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) { for (final NetworkRequest req : nri.mRequests) { cancelNpiRequest(req); @@ -4807,183 +4749,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * 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. @@ -4992,10 +4757,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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) { @@ -5051,25 +4814,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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) { @@ -5157,195 +4901,54 @@ public class ConnectivityService extends IConnectivityManager.Stub 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"); - } + public void setLegacyLockdownVpnEnabled(boolean enabled) { + enforceSettingsPermission(); + mHandler.post(() -> mLockdownEnabled = enabled); } - /** - * 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); - } + private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { + return mLockdownEnabled + && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY + && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID); } - @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); + 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 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; - } + // 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; } - 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(); + // 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; } - } - @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(); - } - } + return nai; + }; - @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(); + private DetailedState getLegacyLockdownState(DetailedState origState) { + if (origState != DetailedState.CONNECTED) { + return origState; } + return (mLockdownEnabled && getLegacyLockdownNai() == null) + ? DetailedState.CONNECTING + : DetailedState.CONNECTED; } @Override @@ -5380,111 +4983,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - 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 onUserAdded(UserHandle user) { + mPermissionMonitor.onUserAdded(user); } - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } + private void onUserRemoved(UserHandle user) { + mPermissionMonitor.onUserRemoved(user); } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -5492,53 +4996,24 @@ public class ConnectivityService extends IConnectivityManager.Stub public void onReceive(Context context, Intent intent) { ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; + final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); + // User should be filled for below intents, check the existence. + if (user == null) { + Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER"); + return; } - // 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); + if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(user); } 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 { + onUserRemoved(user); + } 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<>(); @@ -5645,6 +5120,21 @@ public class ConnectivityService extends IConnectivityManager.Stub final int mUid; @Nullable final String mCallingAttributionTag; + // In order to preserve the mapping of NetworkRequest-to-callback when apps register + // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be + // maintained for keying off of. This is only a concern when the original nri + // mNetworkRequests changes which happens currently for apps that register callbacks to + // track the default network. In those cases, the nri is updated to have mNetworkRequests + // that match the per-app default nri that currently tracks the calling app's uid so that + // callbacks are fired at the appropriate time. When the callbacks fire, + // mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When + // callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue. + // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects. + @NonNull + private final NetworkRequest mNetworkRequestForCallback; + NetworkRequest getNetworkRequestForCallback() { + return mNetworkRequestForCallback; + } /** * Get the list of UIDs this nri applies to. @@ -5660,13 +5150,15 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), pi, callingAttributionTag); + this(Collections.singletonList(r), r, pi, callingAttributionTag); } NetworkRequestInfo(@NonNull final List<NetworkRequest> r, - @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi, + @Nullable String callingAttributionTag) { + ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); - ensureAllNetworkRequestsHaveType(mRequests); + mNetworkRequestForCallback = requestForCallback; mPendingIntent = pi; mMessenger = null; mBinder = null; @@ -5678,15 +5170,17 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, @Nullable final IBinder binder, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), m, binder, callingAttributionTag); + this(Collections.singletonList(r), r, m, binder, callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m, + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m, @Nullable final IBinder binder, @Nullable String callingAttributionTag) { super(); + ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); + mNetworkRequestForCallback = requestForCallback; mMessenger = m; - ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); @@ -5701,12 +5195,26 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + NetworkRequestInfo(@NonNull final NetworkRequestInfo nri, + @NonNull final List<NetworkRequest> r) { + super(); + ensureAllNetworkRequestsHaveType(r); + mRequests = initializeRequests(r); + mNetworkRequestForCallback = nri.getNetworkRequestForCallback(); + mMessenger = nri.mMessenger; + mBinder = nri.mBinder; + mPid = nri.mPid; + mUid = nri.mUid; + mPendingIntent = nri.mPendingIntent; + mCallingAttributionTag = nri.mCallingAttributionTag; + } + NetworkRequestInfo(@NonNull final NetworkRequest r) { this(Collections.singletonList(r)); } NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { - this(r, null /* pi */, null /* callingAttributionTag */); + this(r, r.get(0), null /* pi */, null /* callingAttributionTag */); } // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer @@ -5865,7 +5373,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} // is unused and will be replaced by ones appropriate for the caller. // This allows callers to keep track of the default network for their app. - networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); + networkCapabilities = copyDefaultNetworkCapabilitiesForUid( + defaultNc, callingUid, callingPackageName); enforceAccessPermission(); break; case TRACK_SYSTEM_DEFAULT: @@ -5904,10 +5413,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } ensureValid(networkCapabilities); - NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, + final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); + final NetworkRequestInfo nri = getNriToRegister( + networkRequest, messenger, binder, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5916,7 +5425,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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); } @@ -5928,6 +5438,30 @@ public class ConnectivityService extends IConnectivityManager.Stub return networkRequest; } + /** + * Return the nri to be used when registering a network request. Specifically, this is used with + * requests registered to track the default request. If there is currently a per-app default + * tracking the app requestor, then we need to create a version of this nri that mirrors that of + * the tracking per-app default so that callbacks are sent to the app requestor appropriately. + * @param nr the network request for the nri. + * @param msgr the messenger for the nri. + * @param binder the binder for the nri. + * @param callingAttributionTag the calling attribution tag for the nri. + * @return the nri to register. + */ + private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr, + @Nullable final Messenger msgr, @Nullable final IBinder binder, + @Nullable String callingAttributionTag) { + final List<NetworkRequest> requests; + if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) { + requests = copyDefaultNetworkRequestsForUid( + nr.getRequestorUid(), nr.getRequestorPackageName()); + } else { + requests = Collections.singletonList(nr); + } + return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag); + } + private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, String callingPackageName, String callingAttributionTag) { if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) { @@ -6221,6 +5755,102 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Return the default network request currently tracking the given uid. + * @param uid the uid to check. + * @return the NetworkRequestInfo tracking the given uid. + */ + @NonNull + private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) { + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (nri == mDefaultRequest) { + continue; + } + // Checking the first request is sufficient as only multilayer requests will have more + // than one request and for multilayer, all requests will track the same uids. + if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) { + return nri; + } + } + return mDefaultRequest; + } + + /** + * Get a copy of the network requests of the default request that is currently tracking the + * given uid. + * @param requestorUid the uid to check the default for. + * @param requestorPackageName the requestor's package name. + * @return a copy of the default's NetworkRequest that is tracking the given uid. + */ + @NonNull + private List<NetworkRequest> copyDefaultNetworkRequestsForUid( + @NonNull final int requestorUid, @NonNull final String requestorPackageName) { + return copyNetworkRequestsForUid( + getDefaultRequestTrackingUid(requestorUid).mRequests, + requestorUid, requestorPackageName); + } + + /** + * Copy the given nri's NetworkRequest collection. + * @param requestsToCopy the NetworkRequest collection to be copied. + * @param requestorUid the uid to set on the copied collection. + * @param requestorPackageName the package name to set on the copied collection. + * @return the copied NetworkRequest collection. + */ + @NonNull + private List<NetworkRequest> copyNetworkRequestsForUid( + @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid, + @NonNull final String requestorPackageName) { + final List<NetworkRequest> requests = new ArrayList<>(); + for (final NetworkRequest nr : requestsToCopy) { + requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid( + nr.networkCapabilities, requestorUid, requestorPackageName), + nr.legacyType, nextNetworkRequestId(), nr.type)); + } + return requests; + } + + @NonNull + private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid( + @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid, + @NonNull final String requestorPackageName) { + final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy); + netCap.removeCapability(NET_CAPABILITY_NOT_VPN); + netCap.setSingleUid(requestorUid); + netCap.setUids(new ArraySet<>()); + restrictRequestUidsForCallerAndSetRequestorInfo( + netCap, requestorUid, requestorPackageName); + return netCap; + } + + /** + * Get the nri that is currently being tracked for callbacks by per-app defaults. + * @param nr the network request to check for equality against. + * @return the nri if one exists, null otherwise. + */ + @Nullable + private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) { + for (final NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.getNetworkRequestForCallback().equals(nr)) { + return nri; + } + } + return null; + } + + /** + * Check if an nri is currently being managed by per-app default networking. + * @param nri the nri to check. + * @return true if this nri is currently being managed by per-app default networking. + */ + private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) { + // nri.mRequests.get(0) is only different from the original request filed in + // nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default + // functionality therefore if these two don't match, it means this particular nri is + // currently being managed by a per-app default. + return nri.getNetworkRequestForCallback() != nri.mRequests.get(0); + } + + /** * Determine if an nri is a managed default request that disallows default networking. * @param nri the request to evaluate * @return true if device-default networking is disallowed @@ -7298,13 +6928,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } Bundle bundle = new Bundle(); - // In the case of multi-layer NRIs, the first request is not necessarily the one that - // is satisfied. This is vexing, but the ConnectivityManager code that receives this - // callback is only using the request as a token to identify the callback, so it doesn't - // matter too much at this point as long as the callback can be found. // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects. // TODO: check if defensive copies of data is needed. - final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0)); + final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback(); putParcelable(bundle, nrForCallback); Message msg = Message.obtain(); if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) { @@ -7449,7 +7075,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); - notifyLockdownVpn(newDefaultNetwork); handleApplyDefaultProxy(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getHttpProxy() : null); updateTcpBufferSizes(null != newDefaultNetwork @@ -7907,12 +7532,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); } } @@ -7970,18 +7589,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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); @@ -8020,7 +7627,6 @@ public class ConnectivityService extends IConnectivityManager.Stub oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } - notifyLockdownVpn(networkAgent); if (DBG) { log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from " @@ -8321,34 +7927,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @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( @@ -8427,8 +8005,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8440,44 +8016,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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(), @@ -8569,25 +8107,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @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(); @@ -8624,22 +8143,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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. * @@ -9367,7 +8870,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) { log("set OEM network preferences :" + preference.toString()); } - final List<NetworkRequestInfo> nris = + final ArraySet<NetworkRequestInfo> nris = new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); updateDefaultNetworksForOemNetworkPreference(nris); mOemNetworkPreferences = preference; @@ -9379,27 +8882,88 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateDefaultNetworksForOemNetworkPreference( - @NonNull final List<NetworkRequestInfo> nris) { + @NonNull final Set<NetworkRequestInfo> nris) { + handleRemoveNetworkRequests(mDefaultNetworkRequests); + addPerAppDefaultNetworkRequests(nris); + } + + private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); - clearNonDefaultNetworkAgents(); - addDefaultNetworkRequests(nris); + mDefaultNetworkRequests.addAll(nris); + final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate = + getPerAppCallbackRequestsToUpdate(); + handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate); + final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris); + nrisToRegister.addAll( + createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate)); + handleRegisterNetworkRequests(nrisToRegister); } - 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]); + /** + * All current requests that are tracking the default network need to be assessed as to whether + * or not the current set of per-application default requests will be changing their default + * network. If so, those requests will need to be updated so that they will send callbacks for + * default network changes at the appropriate time. Additionally, those requests tracking the + * default that were previously updated by this flow will need to be reassessed. + * @return the nris which will need to be updated. + */ + private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() { + final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>(); + // Get the distinct nris to check since for multilayer requests, it is possible to have the + // same nri in the map's values for each of its NetworkRequest objects. + final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values()); for (final NetworkRequestInfo nri : nris) { - if (mDefaultRequest != nri) { - handleRemoveNetworkRequest(nri); + // Include this nri if it is currently being tracked. + if (isPerAppTrackedNri(nri)) { + defaultCallbackRequests.add(nri); + continue; + } + // We only track callbacks for requests tracking the default. + if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) { + continue; + } + // Include this nri if it will be tracked by the new per-app default requests. + final boolean isNriGoingToBeTracked = + getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest; + if (isNriGoingToBeTracked) { + defaultCallbackRequests.add(nri); } } + return defaultCallbackRequests; } - private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) { - mDefaultNetworkRequests.addAll(nris); - handleRegisterNetworkRequest(nris); + /** + * Create nris for those network requests that are currently tracking the default network that + * are being controlled by a per-application default. + * @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the + * foundation when creating the nri. Important items include the calling uid's original + * NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These + * requests are assumed to have already been validated as needing to be updated. + * @return the Set of nris to use when registering network requests. + */ + private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister( + @NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) { + final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>(); + for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) { + final NetworkRequestInfo trackingNri = + getDefaultRequestTrackingUid(callbackRequest.mUid); + + // If this nri is not being tracked, the change it back to an untracked nri. + if (trackingNri == mDefaultRequest) { + callbackRequestsToRegister.add(new NetworkRequestInfo( + callbackRequest, + Collections.singletonList(callbackRequest.getNetworkRequestForCallback()))); + continue; + } + + final String requestorPackageName = + callbackRequest.mRequests.get(0).getRequestorPackageName(); + callbackRequestsToRegister.add(new NetworkRequestInfo( + callbackRequest, + copyNetworkRequestsForUid( + trackingNri.mRequests, callbackRequest.mUid, requestorPackageName))); + } + return callbackRequestsToRegister; } /** @@ -9407,9 +8971,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @VisibleForTesting final class OemNetworkRequestFactory { - List<NetworkRequestInfo> createNrisFromOemNetworkPreferences( + ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences( @NonNull final OemNetworkPreferences preference) { - final List<NetworkRequestInfo> nris = new ArrayList<>(); + final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(); final SparseArray<Set<Integer>> uids = createUidsFromOemNetworkPreferences(preference); for (int i = 0; i < uids.size(); i++) { diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index c6a8660d8797..e12586bfdc06 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, 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 41903fcd165f..67f6ec9f9a41 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -902,10 +902,13 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { * 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 @@ public class PackageWatchdog { @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 @@ public class PackageWatchdog { /** Returns the monitored package name. */ private String getName() { - return mPackage.getPackageName(); + return mPackageName; } /** diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 0aee78050929..7f0b9621bb36 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -568,7 +568,7 @@ public final class SensorPrivacyService extends SystemService { // 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 1ad0176d3c5b..2f9819997257 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -939,9 +939,12 @@ class StorageManagerService extends IStorageManager.Stub 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 7f638b9a55a2..915517a4b9ce 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -19,6 +19,7 @@ package com.android.server; 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.internal.util.DumpUtils; 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 @@ final class UiModeManagerService extends SystemService { private NotificationManager mNotificationManager; private StatusBarManager mStatusBarManager; private WindowManagerInternal mWindowManager; + private ActivityTaskManagerInternal mActivityTaskManager; private AlarmManager mAlarmManager; private PowerManager mPowerManager; @@ -366,6 +369,7 @@ final class UiModeManagerService extends SystemService { 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 @@ final class UiModeManagerService extends SystemService { } @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/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java new file mode 100644 index 000000000000..5d89bf1b1d82 --- /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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bedd19b6864a..0b1c1154ba75 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.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentCaptureOptions; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.IIntentReceiver; @@ -5626,6 +5627,23 @@ public class ActivityManagerService extends IActivityManager.Stub ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED; } + @Override + public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid, + final int modeFlags, IBinder callerToken) { + final int size = uris.size(); + int[] res = new int[size]; + // Default value DENIED. + Arrays.fill(res, PackageManager.PERMISSION_DENIED); + + for (int i = 0; i < size; i++) { + final Uri uri = uris.get(i); + final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId()); + res[i] = checkUriPermission(ContentProvider.getUriWithoutUserId(uri), pid, uid, + modeFlags, userId, callerToken); + } + return res; + } + /** * @param uri This uri must NOT contain an embedded userId. * @param userId The userId in which the uri is to be resolved. @@ -10747,6 +10765,15 @@ public class ActivityManagerService extends IActivityManager.Stub ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS]; ss[INDEX_TOTAL_PSS] += dmabufMapped; } + + // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not + // need to be added to kernelUsed. + final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb(); + if (totalDmabufHeapExported >= 0) { + pw.print("DMA-BUF Heaps: "); + pw.println(stringifyKBSize(totalDmabufHeapExported)); + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); if (totalDmabufHeapPool >= 0) { pw.print("DMA-BUF Heaps pool: "); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index fc7a476c6108..c8630fa52973 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1536,6 +1536,14 @@ public class AppProfiler { totalPss -= totalMemtrackGraphics; totalPss += dmabufMapped; } + // These are included in the totalExportedDmabuf above and hence do not need to be added + // to kernelUsed. + final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb(); + if (totalExportedDmabufHeap >= 0) { + memInfoBuilder.append("DMA-BUF Heap: "); + memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap)); + memInfoBuilder.append("\n"); + } final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); if (totalDmabufHeapPool >= 0) { diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 68a084e6d249..8af1b5be1517 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 final class PlaybackActivityMonitor */ public void playerEvent(int piid, int event, int deviceId, int binderUid) { if (DEBUG) { - Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%d)", - piid, deviceId, event)); + Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%s)", + piid, deviceId, AudioPlaybackConfiguration.playerStateToString(event))); } final boolean change; synchronized(mPlayerLock) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 0b78dd01f855..a4b3ac57a4df 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 @@ import java.util.ArrayList; /** * 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 1a7544fc7f01..fc1200a4b42a 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 @@ import java.util.Arrays; /** * 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 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { 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 c3d54c2b7fbb..72c5ee5e78c4 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 @@ import com.android.server.biometrics.sensors.GenerateChallengeClient; /** * 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 722a3b843e12..b1083d410fec 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 @@ import com.android.server.biometrics.sensors.HalClientMonitor; /** * 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 abfda499cf0f..1e3b92dcbf61 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 @@ import java.util.Map; /** * 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 9a0974b472cb..f2a9afce1ff7 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 @@ import java.util.List; /** * 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 acae89928460..d63791c99dd4 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 @@ import java.util.Map; /** * 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 14a46481ddc6..9d977d60e705 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 @@ import java.util.ArrayList; /** * 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 e5edfafcef61..28580dece284 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 @@ import com.android.server.biometrics.sensors.RevokeChallengeClient; /** * 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 6290e001f65b..cc3d8f0e28ba 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 @@ import java.util.ArrayList; /** * 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 13bd1c27d8c8..4cdb68df70af 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java @@ -18,11 +18,11 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.Nullable; import android.hardware.biometrics.face.V1_0.FaceError; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.biometrics.face.V1_0.OptionalBool; import android.hardware.biometrics.face.V1_0.OptionalUint64; import android.hardware.biometrics.face.V1_0.Status; -import android.hardware.biometrics.face.V1_1.IBiometricsFace; import android.os.NativeHandle; import android.os.RemoteException; import android.util.Slog; @@ -129,15 +129,4 @@ public class TestHal extends IBiometricsFace.Stub { return 0; } - @Override - public int enrollRemotely(ArrayList<Byte> hat, int timeoutSec, - ArrayList<Integer> disabledFeatures) { - return 0; - } - - @Override - public int enroll_1_1(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures, - NativeHandle nativeHandle) { - return 0; - } } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index e3757dfc6a59..df83df9a73fb 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 @@ import java.util.Map; * <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 @@ public final class CompatChange extends CompatibilityChangeInfo { 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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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 @@ public final class CompatChange extends CompatibilityChangeInfo { * <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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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 @@ public final class CompatChange extends CompatibilityChangeInfo { * * @param pname Package name to reset to defaults for. */ - void removePackageOverride(String pname) { - if (mPackageOverrides != null) { - if (mPackageOverrides.remove(pname) != null) { - notifyListener(pname); - } - } - if (mDeferredOverrides != null) { - mDeferredOverrides.remove(pname); + boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + Context context) { + if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) { + recheckOverride(pname, allowedState, context); + return true; } + return false; } /** @@ -204,8 +241,8 @@ public final class CompatChange extends CompatibilityChangeInfo { 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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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 @@ public final class CompatChange extends CompatibilityChangeInfo { * @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(); - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + // 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()); + } + } + + // 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 @@ public final class CompatChange extends CompatibilityChangeInfo { 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 6b77b9d4ce39..422991e082a9 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.annotations.VisibleForTesting; 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 @@ final class CompatConfig { 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 @@ final class CompatConfig { * @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 @@ final class CompatConfig { 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 @@ final class CompatConfig { * 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 @@ final class CompatConfig { 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 @@ final class CompatConfig { 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 @@ final class CompatConfig { 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 51ba5f775880..d17753fe81bd 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 static android.os.Process.SYSTEM_UID; 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.AndroidBuildClassifier; 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 com.android.server.LocalServices; 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 @@ public class PlatformCompat extends IPlatformCompat.Stub { @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 @@ public class PlatformCompat extends IPlatformCompat.Stub { @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 397af7ba2991..61b11a5851a9 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_VPN; 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.RaEvent; 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 @@ final public class IpConnectivityEventBuilder { 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 6f112d73ba14..508739f2e1e0 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 @@ public class NetworkNotificationManager { // 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/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 8d21f6f0f59f..8bf188696c27 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -83,9 +83,8 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final INetd mNetd; private final Dependencies mDeps; - // Values are User IDs. @GuardedBy("this") - private final Set<Integer> mUsers = new HashSet<>(); + private final Set<UserHandle> mUsers = new HashSet<>(); // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission. @GuardedBy("this") @@ -173,10 +172,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms); } - final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */); - for (UserHandle user : users) { - mUsers.add(user.getIdentifier()); - } + mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */)); final SparseArray<ArraySet<String>> systemPermission = SystemConfig.getInstance().getSystemPermissions(); @@ -259,16 +255,15 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse return array; } - private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) { + private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) { List<Integer> network = new ArrayList<>(); List<Integer> system = new ArrayList<>(); for (Entry<Integer, Boolean> app : apps.entrySet()) { List<Integer> list = app.getValue() ? system : network; - for (int user : users) { - final UserHandle handle = UserHandle.of(user); - if (handle == null) continue; + for (UserHandle user : users) { + if (user == null) continue; - list.add(UserHandle.getUid(handle, app.getKey())); + list.add(UserHandle.getUid(user, app.getKey())); } } try { @@ -291,14 +286,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserAdded(int user) { - if (user < 0) { - loge("Invalid user in onUserAdded: " + user); - return; - } + public synchronized void onUserAdded(@NonNull UserHandle user) { mUsers.add(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, true); } @@ -310,14 +301,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserRemoved(int user) { - if (user < 0) { - loge("Invalid user in onUserRemoved: " + user); - return; - } + public synchronized void onUserRemoved(@NonNull UserHandle user) { mUsers.remove(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, false); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 33c19b1ca2b2..a769e88f77d7 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.ConnectivityManager.NETID_UNSET; 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 @@ public class Vpn { */ @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 @@ public class Vpn { 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 @@ public class Vpn { } } + /** 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 @@ public class Vpn { agentDisconnect(); jniReset(mInterface); mInterface = null; - mNetworkCapabilities.setUids(null); - mNetworkCapabilities.setTransportInfo(null); + resetNetworkCapabilities(); } // Revoke the connection or stop the VpnRunner. @@ -1229,8 +1244,7 @@ public class Vpn { } 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 @@ public class Vpn { 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/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 1b27572ad8de..09c0937802a5 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -72,11 +72,11 @@ public final class FontManagerService extends IFontManager.Stub { } @Override - public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) { + public int updateFontFile(@NonNull FontUpdateRequest request, int baseVersion) { + Preconditions.checkArgumentNonnegative(baseVersion); Objects.requireNonNull(request); Objects.requireNonNull(request.getFd()); Objects.requireNonNull(request.getSignature()); - Preconditions.checkArgumentNonnegative(baseVersion); getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS, "UPDATE_FONTS permission required."); try { @@ -88,6 +88,21 @@ public final class FontManagerService extends IFontManager.Stub { } } + @Override + public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) { + Preconditions.checkArgumentNonnegative(baseVersion); + Objects.requireNonNull(requests); + getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS, + "UPDATE_FONTS permission required."); + try { + update(baseVersion, requests); + return FontManager.RESULT_SUCCESS; + } catch (SystemFontException e) { + Slog.e(TAG, "Failed to update font family", e); + return e.getErrorCode(); + } + } + /* package */ static class SystemFontException extends AndroidException { private final int mErrorCode; diff --git a/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING new file mode 100644 index 000000000000..7fbf426c71c4 --- /dev/null +++ b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "UpdatableSystemFontTest" + } + ] +} diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 45f2a38b6773..08ddc6ddf4ae 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -45,6 +45,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Manages set of updatable font files. @@ -182,8 +183,12 @@ final class UpdatableFontDir { List<FontConfig.FontFamily> fontFamilies = config.fontFamilies; for (int i = 0; i < fontFamilies.size(); i++) { FontConfig.FontFamily fontFamily = fontFamilies.get(i); - // Ignore failures as updated fonts may be obsoleted by system OTA update. - addFontFamily(fontFamily); + try { + addFontFamily(fontFamily); + } catch (SystemFontException e) { + // Ignore failures as updated fonts may be obsoleted by system OTA update. + Slog.i(TAG, "Obsolete font family: " + fontFamily.getName()); + } } success = true; } catch (Throwable t) { @@ -236,10 +241,7 @@ final class UpdatableFontDir { request.getFd().getFileDescriptor(), request.getSignature()); break; case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY: - // TODO: define error code. - if (!addFontFamily(request.getFontFamily())) { - throw new IllegalArgumentException("Invalid font family"); - } + addFontFamily(request.getFontFamily()); break; } } @@ -495,18 +497,15 @@ final class UpdatableFontDir { * Unnamed font families are used as other named font family's fallback fonts to guarantee a * complete glyph coverage. */ - private boolean addFontFamily(FontConfig.FontFamily fontFamily) { - if (fontFamily.getName() == null) { - Slog.e(TAG, "Name is null."); - return false; - } + private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException { + Objects.requireNonNull(fontFamily.getName()); FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily); if (resolvedFontFamily == null) { - Slog.e(TAG, "Required fonts are not available"); - return false; + throw new SystemFontException( + FontManager.RESULT_ERROR_FONT_NOT_FOUND, + "Required fonts are not available"); } mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily); - return true; } @Nullable diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index cb47bb25f152..86a8e36d748d 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 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { 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 69180643661f..f64efe7c26cc 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 @@ public class HdmiCecConfig { 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 @@ public class HdmiCecConfig { 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 382f0f9d6329..d8914b389191 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 @@ abstract class HdmiCecLocalDevice { FOLLOWER_SAFETY_TIMEOUT); return true; } - return false; + + mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return true; } @ServiceThreadOnly @@ -666,9 +668,8 @@ abstract class HdmiCecLocalDevice { 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 @@ abstract class HdmiCecLocalDevice { } 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 a3e18d161751..8d6bcadb3e2b 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 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/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 47cb43e2d4af..5eec315aa51c 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.RemoteException; 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.gnss.hal.GnssNative; 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.SettingsHelper; 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.LocationProviderManager; 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 @@ public class LocationManagerService extends ILocationManager.Stub { manager.startManager(); if (realProvider != null) { + + // custom logic wrapping all non-passive providers + if (manager != mPassiveManager) { + boolean enableStationaryThrottling = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0; + if (enableStationaryThrottling) { + realProvider = new StationaryThrottlingLocationProvider(manager.getName(), + mInjector, realProvider, mEventLog); + } + } + manager.setRealProvider(realProvider); } mProviderManagers.add(manager); @@ -1368,6 +1386,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final SystemAppForegroundHelper mAppForegroundHelper; private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; private final SystemScreenInteractiveHelper mScreenInteractiveHelper; + private final SystemDeviceStationaryHelper mDeviceStationaryHelper; + private final SystemDeviceIdleHelper mDeviceIdleHelper; private final LocationAttributionHelper mLocationAttributionHelper; private final LocationUsageLogger mLocationUsageLogger; @@ -1391,6 +1411,8 @@ public class LocationManagerService extends ILocationManager.Stub { mAppForegroundHelper = new SystemAppForegroundHelper(context); mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog); mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context); + mDeviceStationaryHelper = new SystemDeviceStationaryHelper(); + mDeviceIdleHelper = new SystemDeviceIdleHelper(context); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); mLocationUsageLogger = new LocationUsageLogger(); } @@ -1451,6 +1473,16 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public DeviceStationaryHelper getDeviceStationaryHelper() { + return mDeviceStationaryHelper; + } + + @Override + public DeviceIdleHelper getDeviceIdleHelper() { + return mDeviceIdleHelper; + } + + @Override public LocationAttributionHelper getLocationAttributionHelper() { return mLocationAttributionHelper; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index cc510fbc38bd..d3c853da9b02 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -19,11 +19,11 @@ package com.android.server.location.contexthub; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.hardware.contexthub.V1_0.ContextHubMsg; -import android.hardware.contexthub.V1_0.IContexthub; import android.hardware.contexthub.V1_0.Result; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubManager; @@ -39,6 +39,7 @@ import android.util.proto.ProtoOutputStream; import com.android.server.location.ClientBrokerProto; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.Set; @@ -67,7 +68,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub /* * The proxy to talk to the Context Hub HAL. */ - private final IContexthub mContextHubProxy; + private final IContextHubWrapper mContextHubProxy; /* * The manager that registered this client. @@ -95,6 +96,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private boolean mRegistered = true; + /** + * String containing an attribution tag that was denoted in the {@link Context} of the + * creator of this broker. This is used when attributing the permissions usage of the broker. + */ + private @Nullable String mAttributionTag; + /* * Internal interface used to invoke client callbacks. */ @@ -176,9 +183,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /* package */ ContextHubClientBroker( - Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager, - ContextHubInfo contextHubInfo, short hostEndPointId, - IContextHubClientCallback callback) { + Context context, IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, + short hostEndPointId, IContextHubClientCallback callback, String attributionTag) { mContext = context; mContextHubProxy = contextHubProxy; mClientManager = clientManager; @@ -187,15 +194,17 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mCallbackInterface = callback; mPendingIntentRequest = new PendingIntentRequest(); mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); + mAttributionTag = attributionTag; mHasAccessContextHubPermission = context.checkCallingPermission( Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; } /* package */ ContextHubClientBroker( - Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager, - ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent, - long nanoAppId) { + Context context, IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, + short hostEndPointId, PendingIntent pendingIntent, long nanoAppId, + String attributionTag) { mContext = context; mContextHubProxy = contextHubProxy; mClientManager = clientManager; @@ -203,6 +212,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mHostEndPointId = hostEndPointId; mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); mPackage = pendingIntent.getCreatorPackage(); + mAttributionTag = attributionTag; mHasAccessContextHubPermission = context.checkCallingPermission( Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; @@ -227,7 +237,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub int contextHubId = mAttachedContextHubInfo.getId(); try { - result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp); + // TODO(166846988): Fill in host permissions before sending a message. + result = mContextHubProxy.sendMessageToHub( + contextHubId, messageToNanoApp, + new ArrayList<String>() /* hostPermissions */); } catch (RemoteException e) { Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " + contextHubId + ")", e); @@ -263,6 +276,21 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * Used to override the attribution tag with a newer value if a PendingIntent broker is + * retrieved. + */ + /* package */ void setAttributionTag(String attributionTag) { + mAttributionTag = attributionTag; + } + + /** + * @return the attribution tag associated with this broker. + */ + /* package */ String getAttributionTag() { + return mAttributionTag; + } + + /** * @return the ID of the context hub this client is attached to */ /* package */ int getAttachedContextHubId() { @@ -508,6 +536,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub String out = "[ContextHubClient "; out += "endpointID: " + getHostEndPointId() + ", "; out += "contextHub: " + getAttachedContextHubId() + ", "; + if (mAttributionTag != null) { + out += "attributionTag: " + getAttributionTag() + ", "; + } if (mPendingIntentRequest.isValid()) { out += "intentCreatorPackage: " + mPackage + ", "; out += "nanoAppId: 0x" + Long.toHexString(mPendingIntentRequest.getNanoAppId()); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java index eda89ab52c6f..0351edb8f218 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.app.PendingIntent; import android.content.Context; import android.hardware.contexthub.V1_0.ContextHubMsg; -import android.hardware.contexthub.V1_0.IContexthub; import android.hardware.location.ContextHubInfo; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; @@ -71,7 +70,7 @@ import java.util.function.Consumer; /* * The proxy to talk to the Context Hub. */ - private final IContexthub mContextHubProxy; + private final IContextHubWrapper mContextHubProxy; /* * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients. @@ -138,7 +137,7 @@ import java.util.function.Consumer; } /* package */ ContextHubClientManager( - Context context, IContexthub contextHubProxy) { + Context context, IContextHubWrapper contextHubProxy) { mContext = context; mContextHubProxy = contextHubProxy; } @@ -148,19 +147,21 @@ import java.util.function.Consumer; * * @param contextHubInfo the object describing the hub this client is attached to * @param clientCallback the callback interface of the client to register + * @param attributionTag an optional attribution tag within the given package * * @return the client interface * * @throws IllegalStateException if max number of clients have already registered */ /* package */ IContextHubClient registerClient( - ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback) { + ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback, + String attributionTag) { ContextHubClientBroker broker; synchronized (this) { short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, clientCallback); + hostEndPointId, clientCallback, attributionTag); mHostEndPointIdToClientMap.put(hostEndPointId, broker); mRegistrationRecordDeque.add( new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); @@ -185,13 +186,15 @@ import java.util.function.Consumer; * @param pendingIntent the callback interface of the client to register * @param contextHubInfo the object describing the hub this client is attached to * @param nanoAppId the ID of the nanoapp to receive Intent events for + * @param attributionTag an optional attribution tag within the given package * * @return the client interface * * @throws IllegalStateException if there were too many registered clients at the service */ /* package */ IContextHubClient registerClient( - ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) { + ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId, + String attributionTag) { ContextHubClientBroker broker; String registerString = "Regenerated"; synchronized (this) { @@ -201,11 +204,15 @@ import java.util.function.Consumer; short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, pendingIntent, nanoAppId); + hostEndPointId, pendingIntent, nanoAppId, attributionTag); mHostEndPointIdToClientMap.put(hostEndPointId, broker); registerString = "Registered"; mRegistrationRecordDeque.add( new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); + } else { + // Update the attribution tag to the latest value provided by the client app in + // case the app was updated and decided to change its tag. + broker.setAttributionTag(attributionTag); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 785e6745087f..2eafe6ad41a0 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -27,10 +27,10 @@ import android.hardware.SensorPrivacyManager; import android.hardware.contexthub.V1_0.AsyncEventType; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; -import android.hardware.contexthub.V1_0.HubAppInfo; -import android.hardware.contexthub.V1_0.IContexthubCallback; import android.hardware.contexthub.V1_0.Result; import android.hardware.contexthub.V1_0.TransactionResult; +import android.hardware.contexthub.V1_2.HubAppInfo; +import android.hardware.contexthub.V1_2.IContexthubCallback; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; import android.hardware.location.ContextHubTransaction; @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.util.Pair; import android.util.proto.ProtoOutputStream; import com.android.internal.util.DumpUtils; @@ -137,7 +138,9 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleClientMsg(ContextHubMsg message) { - handleClientMessageCallback(mContextHubId, message); + handleClientMessageCallback(mContextHubId, message, + Collections.emptyList() /* nanoappPermissions */, + Collections.emptyList() /* messageContentPermissions */); } @Override @@ -156,7 +159,21 @@ public class ContextHubService extends IContextHubService.Stub { } @Override - public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) { + public void handleAppsInfo( + ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> nanoAppInfoList) { + handleQueryAppsCallback(mContextHubId, + ContextHubServiceUtil.toHubAppInfo_1_2(nanoAppInfoList)); + } + + @Override + public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message, + ArrayList<String> messageContentPermissions) { + handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions, + messageContentPermissions); + } + + @Override + public void handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList) { handleQueryAppsCallback(mContextHubId, nanoAppInfoList); } } @@ -174,30 +191,31 @@ public class ContextHubService extends IContextHubService.Stub { return; } - mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper.getHub()); + mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); mTransactionManager = new ContextHubTransactionManager( mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager); - List<ContextHub> hubList; + Pair<List<ContextHub>, List<String>> hubInfo; try { - hubList = mContextHubWrapper.getHub().getHubs(); + hubInfo = mContextHubWrapper.getHubs(); } catch (RemoteException e) { Log.e(TAG, "RemoteException while getting Context Hub info", e); - hubList = Collections.emptyList(); + hubInfo = new Pair(Collections.emptyList(), Collections.emptyList()); } mContextHubIdToInfoMap = Collections.unmodifiableMap( - ContextHubServiceUtil.createContextHubInfoMap(hubList)); + ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first)); mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values()); HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>(); for (int contextHubId : mContextHubIdToInfoMap.keySet()) { ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); IContextHubClient client = mClientManager.registerClient( - contextHubInfo, createDefaultClientCallback(contextHubId)); + contextHubInfo, createDefaultClientCallback(contextHubId), + null /* attributionTag */); defaultClientMap.put(contextHubId, client); try { - mContextHubWrapper.getHub().registerCallback( + mContextHubWrapper.registerCallback( contextHubId, new ContextHubServiceCallback(contextHubId)); } catch (RemoteException e) { Log.e(TAG, "RemoteException while registering service callback for hub (ID = " @@ -596,7 +614,9 @@ public class ContextHubService extends IContextHubService.Stub { * @param contextHubId the ID of the hub the message came from * @param message the message contents */ - private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) { + private void handleClientMessageCallback( + int contextHubId, ContextHubMsg message, List<String> nanoappPermissions, + List<String> messageContentPermissions) { mClientManager.onMessageFromNanoApp(contextHubId, message); } @@ -721,7 +741,7 @@ public class ContextHubService extends IContextHubService.Stub { } ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); - return mClientManager.registerClient(contextHubInfo, clientCallback); + return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag); } /** @@ -745,7 +765,8 @@ public class ContextHubService extends IContextHubService.Stub { } ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); - return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId); + return mClientManager.registerClient( + contextHubInfo, pendingIntent, nanoAppId, attributionTag); } /** diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java index 88ed1053616a..8361253dcd4a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java @@ -23,8 +23,8 @@ import android.content.Context; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; import android.hardware.contexthub.V1_0.HostEndPoint; -import android.hardware.contexthub.V1_0.HubAppInfo; import android.hardware.contexthub.V1_0.Result; +import android.hardware.contexthub.V1_2.HubAppInfo; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubTransaction; import android.hardware.location.NanoAppBinary; @@ -161,7 +161,8 @@ import java.util.Set; ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>(); for (HubAppInfo appInfo : nanoAppInfoList) { nanoAppStateList.add( - new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled)); + new NanoAppState(appInfo.info_1_0.appId, appInfo.info_1_0.version, + appInfo.info_1_0.enabled, appInfo.permissions)); } return nanoAppStateList; @@ -255,4 +256,26 @@ import java.util.Set; return ContextHubTransaction.RESULT_FAILED_UNKNOWN; } } + + /** + * Converts old list of HubAppInfo received from the HAL to V1.2 HubAppInfo objects. + * + * @param oldInfoList list of V1.0 HubAppInfo objects + * @return list of V1.2 HubAppInfo objects + */ + /* package */ + static ArrayList<HubAppInfo> toHubAppInfo_1_2( + ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> oldInfoList) { + ArrayList newAppInfo = new ArrayList<HubAppInfo>(); + for (android.hardware.contexthub.V1_0.HubAppInfo oldInfo : oldInfoList) { + HubAppInfo newInfo = new HubAppInfo(); + newInfo.info_1_0.appId = oldInfo.appId; + newInfo.info_1_0.version = oldInfo.version; + newInfo.info_1_0.memUsage = oldInfo.memUsage; + newInfo.info_1_0.enabled = oldInfo.enabled; + newInfo.permissions = new ArrayList<String>(); + newAppInfo.add(newInfo); + } + return newAppInfo; + } } diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index c11e289c116b..c1d63dd2fbc9 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -16,11 +16,17 @@ package com.android.server.location.contexthub; import android.annotation.Nullable; +import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_1.Setting; import android.hardware.contexthub.V1_1.SettingValue; +import android.hardware.contexthub.V1_2.IContexthubCallback; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.NoSuchElementException; /** @@ -87,6 +93,24 @@ public abstract class IContextHubWrapper { } /** + * Calls the appropriate getHubs function depending on the HAL version. + */ + public abstract Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException; + + /** + * Calls the appropriate registerCallback function depending on the HAL version. + */ + public abstract void registerCallback( + int hubId, IContexthubCallback callback) throws RemoteException; + + /** + * Calls the appropriate sendMessageToHub function depending on the HAL version. + */ + public abstract int sendMessageToHub( + int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, + ArrayList<String> hostPermissions) throws RemoteException; + + /** * @return A valid instance of Contexthub HAL 1.0. */ public abstract android.hardware.contexthub.V1_0.IContexthub getHub(); @@ -148,6 +172,21 @@ public abstract class IContextHubWrapper { mHub = hub; } + public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException { + return new Pair(mHub.getHubs(), new ArrayList<String>()); + } + + public void registerCallback( + int hubId, IContexthubCallback callback) throws RemoteException { + mHub.registerCallback(hubId, callback); + } + + public int sendMessageToHub( + int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, + ArrayList<String> hostPermissions) throws RemoteException { + return mHub.sendMessageToHub(hubId, message); + } + public android.hardware.contexthub.V1_0.IContexthub getHub() { return mHub; } @@ -188,6 +227,21 @@ public abstract class IContextHubWrapper { mHub = hub; } + public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException { + return new Pair(mHub.getHubs(), new ArrayList<String>()); + } + + public void registerCallback( + int hubId, IContexthubCallback callback) throws RemoteException { + mHub.registerCallback(hubId, callback); + } + + public int sendMessageToHub( + int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, + ArrayList<String> hostPermissions) throws RemoteException { + return mHub.sendMessageToHub(hubId, message); + } + public android.hardware.contexthub.V1_0.IContexthub getHub() { return mHub; } @@ -227,13 +281,42 @@ public abstract class IContextHubWrapper { } } - private static class ContextHubWrapperV1_2 extends IContextHubWrapper { - private android.hardware.contexthub.V1_2.IContexthub mHub; + private static class ContextHubWrapperV1_2 extends IContextHubWrapper + implements android.hardware.contexthub.V1_2.IContexthub.getHubs_1_2Callback { + private final android.hardware.contexthub.V1_2.IContexthub mHub; + + private Pair<List<ContextHub>, List<String>> mHubInfo = + new Pair<>(Collections.emptyList(), Collections.emptyList()); ContextHubWrapperV1_2(android.hardware.contexthub.V1_2.IContexthub hub) { mHub = hub; } + @Override + public void onValues(ArrayList<ContextHub> hubs, ArrayList<String> supportedPermissions) { + mHubInfo = new Pair(hubs, supportedPermissions); + } + + public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException { + mHub.getHubs_1_2(this); + return mHubInfo; + } + + public void registerCallback( + int hubId, IContexthubCallback callback) throws RemoteException { + mHub.registerCallback_1_2(hubId, callback); + } + + public int sendMessageToHub( + int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, + ArrayList<String> hostPermissions) throws RemoteException { + android.hardware.contexthub.V1_2.ContextHubMsg newMessage = + new android.hardware.contexthub.V1_2.ContextHubMsg(); + newMessage.msg_1_0 = message; + newMessage.permissions = hostPermissions; + return mHub.sendMessageToHub_1_2(hubId, newMessage); + } + public android.hardware.contexthub.V1_0.IContexthub getHub() { return mHub; } diff --git a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java index 60109fe4b9f6..667fb98f8649 100644 --- a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java +++ b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java @@ -17,7 +17,7 @@ package com.android.server.location.contexthub; import android.annotation.Nullable; -import android.hardware.contexthub.V1_0.HubAppInfo; +import android.hardware.contexthub.V1_2.HubAppInfo; import android.hardware.location.NanoAppInstanceInfo; import android.util.Log; @@ -154,8 +154,8 @@ import java.util.function.Consumer; synchronized void updateCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) { HashSet<Long> nanoAppIdSet = new HashSet<>(); for (HubAppInfo appInfo : nanoAppInfoList) { - handleQueryAppEntry(contextHubId, appInfo.appId, appInfo.version); - nanoAppIdSet.add(appInfo.appId); + handleQueryAppEntry(contextHubId, appInfo.info_1_0.appId, appInfo.info_1_0.version); + nanoAppIdSet.add(appInfo.info_1_0.appId); } Iterator<NanoAppInstanceInfo> iterator = mNanoAppHash.values().iterator(); diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 865d41f1baee..dbfd0a52b013 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 @@ public class LocationEventLog extends LocalEventLog { private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6; private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7; private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8; - private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9; + private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9; + private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10; @GuardedBy("mAggregateStats") private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats; @@ -167,6 +168,11 @@ public class LocationEventLog extends LocalEventLog { getAggregateStats(provider, identity.getPackageName()).markLocationDelivered(); } + /** Logs that a provider has entered or exited stationary throttling. */ + public void logProviderStationaryThrottled(String provider, boolean throttled) { + addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled); + } + /** Logs that the location power save mode has changed. */ public void logLocationPowerSaveMode( @LocationPowerSaveMode int locationPowerSaveMode) { @@ -198,6 +204,9 @@ public class LocationEventLog extends LocalEventLog { case EVENT_PROVIDER_DELIVER_LOCATION: return new ProviderDeliverLocationEvent(timeDelta, (String) args[0], (Integer) args[1], (CallerIdentity) args[2]); + case EVENT_PROVIDER_STATIONARY_THROTTLED: + return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0], + (Boolean) args[1]); case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE: return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]); default: @@ -332,6 +341,23 @@ public class LocationEventLog extends LocalEventLog { } } + private static final class ProviderStationaryThrottledEvent extends ProviderEvent { + + private final boolean mStationaryThrottled; + + private ProviderStationaryThrottledEvent(long timeDelta, String provider, + boolean stationaryThrottled) { + super(timeDelta, provider); + mStationaryThrottled = stationaryThrottled; + } + + @Override + public String getLogString() { + return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled" + : "unthrottled"); + } + } + private static final class LocationPowerSaveModeEvent extends LogEvent { @LocationPowerSaveMode diff --git a/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java new file mode 100644 index 000000000000..e820b00f3714 --- /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 000000000000..b77c0f7dfe7f --- /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 0e157c22a32b..b0351184ffe5 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 @@ public interface Injector { /** 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 000000000000..6a89079d81ba --- /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 000000000000..6f0e681b808f --- /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 e22a0145f2bf..4e0a0b89f238 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 @@ public abstract class AbstractLocationProvider { @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 000000000000..a3ec867220dd --- /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 000000000000..6f4aa642500f --- /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 32d637ffa730..4c97f645aac9 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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { } @Override - public void onSetRequest(ProviderRequest request) { + protected void onSetRequest(ProviderRequest request) { mRequest = request; mServiceWatcher.runOnBinder(binder -> { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java index 38eeb88e63b0..af0774c6c3fa 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 @@ class RebootEscrowData { */ 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 @@ class RebootEscrowData { 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 @@ class RebootEscrowData { 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 53b62ca6ecb5..30ea5556b41c 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -146,6 +146,7 @@ class RebootEscrowManager { RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { + Slog.i(TAG, "Using server based resume on reboot"); rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); @@ -272,6 +273,10 @@ class RebootEscrowManager { // generated before reboot. Note that we will clear the escrow key even if the keystore key // is null. SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); + if (kk == null) { + Slog.i(TAG, "Failed to load the key for resume on reboot from key store."); + } + RebootEscrowKey escrowKey; try { escrowKey = getAndClearRebootEscrowKey(kk); @@ -281,7 +286,7 @@ class RebootEscrowManager { return; } - if (kk == null || escrowKey == null) { + if (escrowKey == null) { onGetRebootEscrowKeyFailed(users); return; } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index 9d09637cdc74..b3b45460899d 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -136,6 +136,11 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); return null; } + if (decryptionKey == null) { + Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is" + + " null."); + return null; + } Slog.i(TAG, "Loaded reboot escrow server blob from storage"); try { diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java index d48b9a4e7879..76bdf44e9df2 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -83,7 +83,14 @@ public final class MediaMetricsManagerService extends SystemService { @Override public void reportPlaybackStateEvent( String sessionId, PlaybackStateEvent event, int userId) { - // TODO: log it to statsd + StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(322) + .writeString(sessionId) + .writeInt(event.getState()) + .writeLong(event.getTimeSinceCreatedMillis()) + .usePooledBuffer() + .build(); + StatsLog.write(statsEvent); } @Override diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 6d1c68039ee5..3cc32bef0e67 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.NotificationManager; 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 @@ public class LockdownVpnTracker { @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 @@ public class LockdownVpnTracker { * 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 @@ public class LockdownVpnTracker { 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 @@ public class LockdownVpnTracker { 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 @@ public class LockdownVpnTracker { 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 class LockdownVpnTracker { } } - 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/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index e91bb46657e1..b06e84d9df4e 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; @@ -935,6 +936,17 @@ public class LauncherAppsService extends SystemService { 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 214fd4400ac1..e6789d4ba8ac 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -461,6 +461,7 @@ import java.util.concurrent.atomic.AtomicBoolean; 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; /** @@ -2585,49 +2586,42 @@ public class PackageManagerService extends IPackageManager.Stub 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++) { + // First, try to use approved apps. + 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; + // 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 @@ public class PackageManagerService extends IPackageManager.Stub } } - // 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } /** + * 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 @@ public class PackageManagerService extends IPackageManager.Stub 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, diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 5364cbfede86..a83a3f81bc00 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 a8a6bcec2313..2112247650a5 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.SystemClock; 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 @@ public final class Settings implements Watchable, Snappable { } else { serializer.attributeInt(null, "sharedUserId", pkg.appId); } + serializer.attributeFloat(null, "loadingProgress", + pkg.getIncrementalStates().getProgress()); writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions); @@ -3389,6 +3390,9 @@ public final class Settings implements Watchable, Snappable { 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 @@ public final class Settings implements Watchable, Snappable { 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 9b092c000172..bb4ec16be0a8 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { String bitmapPath; String iconUri; final String locusIdString; + int splashScreenThemeResId; int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); @@ -1871,6 +1874,8 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 d3aace19672a..c06f01a463ad 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 @@ public class ShortcutParser { 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 @@ public class ShortcutParser { disabledMessageResId, rank, iconResId, - enabled); + enabled, + splashScreenTheme); } finally { sa.recycle(); } @@ -413,7 +416,7 @@ public class ShortcutParser { 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 @@ public class ShortcutParser { 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 863e3fe5c6a3..4d8abea8acd4 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 @@ public class ShortcutService extends IShortcutService.Stub { } @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/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 37dfea4ee0f3..5ee612b6d55f 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 class AndroidPackageUtils { public static void validatePackageDexMetadata(AndroidPackage pkg) throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); + String packageName = pkg.getPackageName(); + long versionCode = pkg.toAppInfoWithoutState().longVersionCode; for (String dexMetadata : apkToDexMetadataList) { - DexMetadataHelper.validateDexMetadataFile(dexMetadata); + DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index 1925590112f8..b3108c58a11e 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.DomainVerificationStateMap; 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 @@ public class DomainVerificationDebug { 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 50fd6e3ddea1..5d4370ae5dd4 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 com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; 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 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan 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 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan DomainVerificationCollector getCollector(); /** - * Check if a resolving URI is approved to takeover the domain as the sole resolved target. + * 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 + */ + @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. + * 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. */ - boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + @ApprovalLevel + int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, @UserIdInt int userId); /** @@ -231,8 +325,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan 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 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan @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 679f948bb3de..c864b2937f6b 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 class DomainVerificationPersistence { 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 @@ public class DomainVerificationPersistence { 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 @@ public class DomainVerificationPersistence { 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 @@ public class DomainVerificationPersistence { 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 fa0327414914..86a92d792026 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.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.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.os.UserHandle; 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.Collection; 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 @@ public class DomainVerificationService extends SystemService } pkgState.getOrCreateUserSelectionState(userId) - .setDisallowLinkHandling(!allowed); + .setLinkHandlingAllowed(allowed); } mConnection.scheduleWriteSettings(); @@ -423,11 +429,11 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService } pkgState.getOrCreateUserSelectionState(userId) - .setDisallowLinkHandling(!allowed); + .setLinkHandlingAllowed(allowed); } } @@ -468,6 +474,17 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService } return new DomainVerificationUserSelection(pkgState.getId(), packageName, - UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap); + UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap); } } @@ -683,7 +700,7 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService // 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 @@ public class DomainVerificationService extends SystemService @Nullable Function<String, PackageSetting> pkgSettingFunction) throws NameNotFoundException { if (pkgSettingFunction == null) { - pkgSettingFunction = mConnection; + pkgSettingFunction = mConnection::getPackageSettingLocked; } synchronized (mLock) { @@ -1156,46 +1173,211 @@ public class DomainVerificationService extends SystemService 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 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 boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + 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 @@ public class DomainVerificationService extends SystemService 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; } - ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); - // 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; + if (userState != null && !userState.isLinkHandlingAllowed()) { + if (DEBUG_APPROVAL) { + debugApproval(packageName, debugObject, userId, false, + "link handling not allowed"); } + return APPROVAL_LEVEL_NONE; + } - // 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; - } + // 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; + } + } - 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; + ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); + // 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; + } + + // 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"); } + 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 @@ public class DomainVerificationService extends SystemService 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 073967e00134..a8e937cf2b90 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.content.pm.verify.domain.DomainVerificationState; 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 @@ class DomainVerificationSettings { 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 8e8260899a48..22468640800e 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 com.android.internal.util.DataClass; 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 @@ public class DomainVerificationUserState { @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 @@ public class DomainVerificationUserState { } + // 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 @@ public class DomainVerificationUserState { * * @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 @@ public class DomainVerificationUserState { 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 @@ public class DomainVerificationUserState { return "DomainVerificationUserState { " + "userId = " + mUserId + ", " + "enabledHosts = " + mEnabledHosts + ", " + - "disallowLinkHandling = " + mDisallowLinkHandling + + "linkHandlingAllowed = " + mLinkHandlingAllowed + " }"; } @@ -154,7 +166,7 @@ public class DomainVerificationUserState { return true && mUserId == that.mUserId && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts) - && mDisallowLinkHandling == that.mDisallowLinkHandling; + && mLinkHandlingAllowed == that.mLinkHandlingAllowed; } @Override @@ -166,15 +178,15 @@ public class DomainVerificationUserState { int _hash = 1; _hash = 31 * _hash + mUserId; _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts); - _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling); + _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed); return _hash; } @DataClass.Generated( - time = 1608234273324L, + time = 1612894390039L, codegenVersion = "1.0.22", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java", - inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java", + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 88fdc4aad5cf..c0b820293649 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -5138,10 +5138,15 @@ public final class PowerManagerService extends SystemService // Get current time before acquiring the lock so that the calculated end time is as // accurate as possible. final long nowElapsed = SystemClock.elapsedRealtime(); - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BATTERY_PREDICTION) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, "setBatteryDischargePrediction"); + } final long timeRemainingMs = timeRemaining.getDuration().toMillis(); - // A non-positive number means the battery should be dead right now... + // A non-positive number means the battery should be dead right now... Preconditions.checkArgumentPositive(timeRemainingMs, "Given time remaining is not positive: " + timeRemainingMs); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 0d4360058788..15c72b34dbc0 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.BackgroundThread; 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 @@ public class StatsPullAtomService extends SystemService { } private void registerCpuTimePerClusterFreq() { - if (KernelCpuTotalBpfMapReader.isSupported()) { + if (KernelCpuBpfTracking.isSupported()) { int tagId = FrameworkStatsLog.CPU_TIME_PER_CLUSTER_FREQ; PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {3}) @@ -1649,17 +1650,18 @@ public class StatsPullAtomService extends SystemService { } private void registerCpuCyclesPerThreadGroupCluster() { - // TODO(b/173227907): Register only when supported. - int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER; - PullAtomMetadata metadata = new PullAtomMetadata.Builder() - .setAdditiveFields(new int[] {3, 4}) - .build(); - mStatsManager.setPullAtomCallback( - tagId, - metadata, - DIRECT_EXECUTOR, - mStatsCallbackImpl - ); + if (KernelCpuBpfTracking.isSupported()) { + int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER; + PullAtomMetadata metadata = new PullAtomMetadata.Builder() + .setAdditiveFields(new int[] {3, 4}) + .build(); + mStatsManager.setPullAtomCallback( + tagId, + metadata, + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } } int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) { @@ -1718,7 +1720,7 @@ public class StatsPullAtomService extends SystemService { } 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 eb4a0501a953..0087c0c29853 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 @@ public final class StorageSessionController { } /** + * 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 13cceeed84e6..709d558ea0bc 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 @@ public final class StorageUserConnection { } /** + * 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 @@ public final class StorageUserConnection { @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 @@ public final class StorageUserConnection { for (CompletableFuture<Void> op : mOutstandingOps) { op.cancel(true); } + for (CompletableFuture<Long> op : mOutstandingTimeoutOps) { + op.cancel(true); + } mOutstandingOps.clear(); } @@ -264,27 +289,44 @@ public final class StorageUserConnection { } } - 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 final class StorageUserConnection { 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 final class StorageUserConnection { 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 final class StorageUserConnection { 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 final class StorageUserConnection { 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 final class StorageUserConnection { } } + 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/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 37d13fb86dc0..12590eba81f8 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 static com.android.server.VcnManagementService.VDBG; 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.LinkAddress; 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.Handler; 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; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; @@ -120,6 +127,15 @@ import java.util.concurrent.TimeUnit; * +----------------------------+ * </pre> * + * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link + * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link + * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the + * lack of WakeLocks). + * + * <p>Any attempt to remove messages from the Handler should be done using {@link + * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when + * no messages remain in the Handler queue. + * * @hide */ public class VcnGatewayConnection extends StateMachine { @@ -128,6 +144,18 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM"; + private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; @@ -138,11 +166,15 @@ public class VcnGatewayConnection extends StateMachine { private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; private static final int TOKEN_ALL = Integer.MIN_VALUE; - private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; @VisibleForTesting(visibility = Visibility.PRIVATE) static final int TEARDOWN_TIMEOUT_SECONDS = 5; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int SAFEMODE_TIMEOUT_SECONDS = 30; + private interface EventInfo {} /** @@ -385,6 +417,23 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/178426520): implement handling of this event private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + /** + * Sent when this VcnGatewayConnection has entered Safemode. + * + * <p>A VcnGatewayConnection enters Safemode when it takes over {@link + * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}. + * + * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link + * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down + * its VcnGatewayConnectin(s). + * + * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not + * validated yet), and RetryTimeoutState. + * + * @param arg1 The "all" token; this signal is always honored. + */ + private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -419,6 +468,21 @@ public class VcnGatewayConnection extends StateMachine { @Nullable private IpSecTunnelInterface mTunnelIface = null; + /** + * WakeLock to be held when processing messages on the Handler queue. + * + * <p>Used to prevent the device from going to sleep while there are VCN-related events to + * process for this VcnGatewayConnection. + * + * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the + * Handler queue have been processed, the WakeLock can be released and cleared. + * + * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send + * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock + * before enquing the delayed event to the Handler. + */ + @NonNull private final VcnWakeLock mWakeLock; + /** Running state of this VcnGatewayConnection. */ private boolean mIsRunning = true; @@ -481,7 +545,13 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable * otherwise. */ - private NetworkAgent mNetworkAgent; + @VisibleForTesting(visibility = Visibility.PRIVATE) + NetworkAgent mNetworkAgent; + + @Nullable private WakeupMessage mTeardownTimeoutAlarm; + @Nullable private WakeupMessage mDisconnectRequestAlarm; + @Nullable private WakeupMessage mRetryTimeoutAlarm; + @Nullable private WakeupMessage mSafemodeTimeoutAlarm; public VcnGatewayConnection( @NonNull VcnContext vcnContext, @@ -518,6 +588,9 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); + mWakeLock = + mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG); + mUnderlyingNetworkTracker = mDeps.newUnderlyingNetworkTracker( mVcnContext, @@ -544,7 +617,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Once torn down, this VcnTunnel CANNOT be started again. */ public void teardownAsynchronously() { - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); @@ -560,6 +633,13 @@ public class VcnGatewayConnection extends StateMachine { mTunnelIface.close(); } + releaseWakeLock(); + + cancelTeardownTimeoutAlarm(); + cancelDisconnectRequestAlarm(); + cancelRetryTimeoutAlarm(); + cancelSafemodeAlarm(); + mUnderlyingNetworkTracker.teardown(); } @@ -576,79 +656,324 @@ public class VcnGatewayConnection extends StateMachine { 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); + } + + /** + * 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 sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) { - super.sendMessageDelayed(what, token, arg2, data, timeout); + private void setRetryTimeoutAlarm(long delay) { + // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In + // either case, there is nothing to cancel. + if (mRetryTimeoutAlarm != null) { + Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set"); + } + + final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken); + mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay); + } + + private void cancelRetryTimeoutAlarm() { + if (mRetryTimeoutAlarm != null) { + mRetryTimeoutAlarm.cancel(); + mRetryTimeoutAlarm = null; + } + + removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setSafemodeAlarm() { + // Only schedule a NEW alarm if none is already set. + if (mSafemodeTimeoutAlarm != null) { + return; + } + + final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL); + mSafemodeTimeoutAlarm = + createScheduledAlarm( + SAFEMODE_TIMEOUT_ALARM, + delayedMessage, + TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + } + + private void cancelSafemodeAlarm() { + if (mSafemodeTimeoutAlarm != null) { + mSafemodeTimeoutAlarm.cancel(); + mSafemodeTimeoutAlarm = null; + } + + removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED); } private void sessionLost(int token, @Nullable Exception exception) { - sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); + sendMessageAndAcquireWakeLock( + EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); } private void sessionClosed(int token, @Nullable Exception exception) { // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the // Disconnecting state. sessionLost(token, exception); - sendMessage(EVENT_SESSION_CLOSED, token); + sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token); } private void childTransformCreated( int token, @NonNull IpSecTransform transform, int direction) { - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_TRANSFORM_CREATED, token, new EventTransformCreatedInfo(direction, transform)); } private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) { - sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); + sendMessageAndAcquireWakeLock( + EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); } private abstract class BaseState extends State { @@ -658,7 +983,7 @@ public class VcnGatewayConnection extends StateMachine { enterState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( @@ -669,22 +994,47 @@ public class VcnGatewayConnection extends StateMachine { protected void enterState() throws Exception {} /** + * Returns whether the given token is valid. + * + * <p>By default, States consider any and all token to be 'valid'. + * + * <p>States should override this method if they want to restrict message handling to + * specific tokens. + */ + protected boolean isValidToken(int token) { + return true; + } + + /** * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng * builds. + * + * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once + * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST + * ensure that mWakeLock is correctly released. */ @Override - public boolean processMessage(Message msg) { + public final boolean processMessage(Message msg) { + final int token = msg.arg1; + if (!isValidToken(token)) { + Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what); + return HANDLED; + } + try { processStateMsg(msg); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); } + // Attempt to release the WakeLock - only possible if the Handler queue is empty + maybeReleaseWakeLock(); + return HANDLED; } @@ -696,7 +1046,7 @@ public class VcnGatewayConnection extends StateMachine { exitState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( @@ -774,6 +1124,8 @@ public class VcnGatewayConnection extends StateMachine { if (mIkeSession != null || mNetworkAgent != null) { Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); } + + cancelSafemodeAlarm(); } @Override @@ -797,27 +1149,16 @@ public class VcnGatewayConnection extends StateMachine { break; } } - } - 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 void exitState() { + // Safe to blindly set up, as it is cancelled and cleared on entering this state + setSafemodeAlarm(); } + } + private abstract class ActiveBaseState extends BaseState { + @Override protected boolean isValidToken(int token) { return (token == TOKEN_ALL || token == mCurrentToken); } @@ -848,7 +1189,7 @@ public class VcnGatewayConnection extends StateMachine { protected void enterState() throws Exception { if (mIkeSession == null) { Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state."); - sendMessage(EVENT_SESSION_CLOSED, mCurrentToken); + sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken); return; } @@ -860,10 +1201,9 @@ public class VcnGatewayConnection extends StateMachine { } mIkeSession.close(); - sendMessageDelayed( - EVENT_TEARDOWN_TIMEOUT_EXPIRED, - mCurrentToken, - TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + + // Safe to blindly set up, as it is cancelled and cleared on exiting this state + setTeardownTimeoutAlarm(); } @Override @@ -905,6 +1245,10 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectedState); } break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -914,6 +1258,8 @@ public class VcnGatewayConnection extends StateMachine { @Override protected void exitState() throws Exception { mSkipRetryTimeout = false; + + cancelTeardownTimeoutAlarm(); } } @@ -985,6 +1331,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1028,6 +1378,14 @@ public class VcnGatewayConnection extends StateMachine { public void unwanted() { teardownAsynchronously(); } + + @Override + public void onValidationStatus( + @ValidationStatus int status, @Nullable Uri redirectUri) { + if (status == NetworkAgent.VALIDATION_STATUS_VALID) { + clearFailedAttemptCounterAndSafeModeAlarm(); + } + } }; agent.register(); @@ -1036,6 +1394,14 @@ public class VcnGatewayConnection extends StateMachine { return agent; } + protected void clearFailedAttemptCounterAndSafeModeAlarm() { + mVcnContext.ensureRunningOnLooperThread(); + + // Validated connection, clear failed attempt counter + mFailedAttempts = 0; + cancelSafemodeAlarm(); + } + protected void applyTransform( int token, @NonNull IpSecTunnelInterface tunnelIface, @@ -1043,7 +1409,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform transform, int direction) { try { - // TODO: Set underlying network of tunnel interface + // TODO(b/180163196): Set underlying network of tunnel interface // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); @@ -1115,9 +1481,6 @@ public class VcnGatewayConnection extends StateMachine { teardownAsynchronously(); } } - - // Successful connection, clear failed attempt counter - mFailedAttempts = 0; } @Override @@ -1154,6 +1517,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1173,6 +1540,7 @@ public class VcnGatewayConnection extends StateMachine { // mUnderlying assumed non-null, given check above. // If network changed, migrate. Otherwise, update any existing networkAgent. if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { + Slog.v(TAG, "Migrating to new network: " + mUnderlying.network); mIkeSession.setNetwork(mUnderlying.network); } else { // oldUnderlying is non-null & underlying network itself has not changed @@ -1196,8 +1564,19 @@ public class VcnGatewayConnection extends StateMachine { mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); } else { updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); + + // mNetworkAgent not null, so the VCN Network has already been established. Clear + // the failed attempt counter and safe mode alarm since this transition is complete. + clearFailedAttemptCounterAndSafeModeAlarm(); } } + + @Override + protected void exitState() { + // Attempt to set the safe mode alarm - this requires the Vcn Network being validated + // while in ConnectedState (which cancels the previous alarm) + setSafemodeAlarm(); + } } /** @@ -1215,8 +1594,8 @@ public class VcnGatewayConnection extends StateMachine { Slog.wtf(TAG, "Underlying network was null in retry state"); transitionTo(mDisconnectedState); } else { - sendMessageDelayed( - EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs()); + // Safe to blindly set up, as it is cancelled and cleared on exiting this state + setRetryTimeoutAlarm(getNextRetryIntervalsMs()); } } @@ -1229,8 +1608,6 @@ public class VcnGatewayConnection extends StateMachine { // If new underlying is null, all networks were lost; go back to disconnected. if (mUnderlying == null) { - removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); - transitionTo(mDisconnectedState); return; } else if (oldUnderlying != null @@ -1241,19 +1618,26 @@ public class VcnGatewayConnection extends StateMachine { // Fallthrough case EVENT_RETRY_TIMEOUT_EXPIRED: - removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); - transitionTo(mConnectingState); break; case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; } } + @Override + public void exitState() { + cancelRetryTimeoutAlarm(); + } + private long getNextRetryIntervalsMs() { final int retryDelayIndex = mFailedAttempts - 1; final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs(); @@ -1425,6 +1809,15 @@ public class VcnGatewayConnection extends StateMachine { } @Override + public void onIpSecTransformsMigrated( + @NonNull IpSecTransform inIpSecTransform, + @NonNull IpSecTransform outIpSecTransform) { + Slog.v(TAG, "ChildTransformsMigrated; token " + mToken); + onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN); + onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT); + } + + @Override public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) { // Nothing to be done; no references to the IpSecTransform are held, and this transform // will be closed by the IKE library. @@ -1526,6 +1919,26 @@ public class VcnGatewayConnection extends StateMachine { ikeSessionCallback, childSessionCallback); } + + /** Builds a new WakeLock. */ + public VcnWakeLock newWakeLock( + @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) { + return new VcnWakeLock(context, wakeLockFlag, wakeLockTag); + } + + /** Builds a new WakeupMessage. */ + public WakeupMessage newWakeupMessage( + @NonNull VcnContext vcnContext, + @NonNull Handler handler, + @NonNull String tag, + @NonNull Runnable runnable) { + return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable); + } + + /** Gets the elapsed real time since boot, in millis. */ + public long getElapsedRealTime() { + return SystemClock.elapsedRealtime(); + } } /** @@ -1605,4 +2018,34 @@ public class VcnGatewayConnection extends StateMachine { 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 39687231c249..685dce4683d7 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.hardware.input.InputManager; 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 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen 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 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen @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 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen } /** - * 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 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen 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 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen 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 e0f5408a1537..607da3ce6fe2 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.VibrationAttributes; 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 @@ public class Vibration { 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 @@ public class Vibration { } /** 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 @@ public class Vibration { 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 @@ public class Vibration { } 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 0fa4fe16e1ba..10393f682279 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.List; 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 @@ public final class VibrationScaler { 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 8910bdfa1c05..334129d6bde9 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 android.util.proto.ProtoOutputStream; 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 @@ public final class VibrationSettings { @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 @@ public final class VibrationSettings { /** 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 04dac7c2b198..389326769096 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.List; 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi 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,7 +91,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi @GuardedBy("mLock") private boolean mForceStop; - public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, + VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { mVibration = vib; @@ -169,7 +168,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } - public Vibration getVibration() { + Vibration getVibration() { return mVibration; } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 9dcf12caa55b..95f6059c482e 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 com.android.internal.annotations.VisibleForTesting; 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 @@ public final class VibratorController { 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 @@ public final class VibratorController { } } - @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 index d264f8570cf2..175085475b6c 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,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; @@ -56,12 +56,8 @@ 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.LocalServices; +import com.android.server.SystemService; import libcore.util.NativeAllocationRegistry; @@ -356,10 +352,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } - VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock, - mBatteryStatsService, mVibrationCallbacks); - - ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread); + ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib); if (ignoreStatus != null) { endVibrationLocked(vib, ignoreStatus); return; @@ -370,7 +363,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mCurrentVibration != null) { mCurrentVibration.cancel(); } - Vibration.Status status = startVibrationLocked(vibThread); + Vibration.Status status = startVibrationLocked(vib); if (status != Vibration.Status.RUNNING) { endVibrationLocked(vib, status); } @@ -495,19 +488,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @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); } @@ -599,8 +592,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @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; } @@ -967,7 +960,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** 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); @@ -1167,6 +1160,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + pw.println(); pw.println(" Previous external vibrations:"); for (Vibration.DebugInfo info : mPreviousExternalVibrations) { pw.println(" " + info); @@ -1181,46 +1175,48 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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(); @@ -1300,7 +1296,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { cancelingVibration.join(); } catch (InterruptedException e) { Slog.w("Interrupted while waiting for vibration to finish before starting " - + "external control", e); + + "external control", e); } } if (DEBUG) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 5697564ce93f..370d921de2af 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.annotation.NonNull; 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.BitmapFactory; 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.IWallpaperService; 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.EventLogTags; 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } @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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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 771b712cf480..c63a0f093dea 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 @@ class ActivityClientController extends IActivityClientController.Stub { } @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 @@ class ActivityClientController extends IActivityClientController.Stub { } /** + * 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 68a2c5d5233c..9bf6df41a93b 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.ACTIVITY_TYPE_UNDEFINED; 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.ResumeActivityItem; 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.LayoutParams; 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (_createTime > 0) { createTime = _createTime; } + mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName); } /** @@ -1807,7 +1836,85 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + mDisplayContent.handleActivitySizeCompatModeIfNeeded(this); mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); } @@ -5931,7 +6123,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 class ActivityRecord extends WindowToken implements WindowManagerService.A 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 79f8229c6162..37fda4ce217a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1997,8 +1997,7 @@ class ActivityStarter { 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 7d2075cca84d..94379b1f230e 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 @@ public abstract class ActivityTaskManagerInternal { * @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 f16a646d00a1..2e98c2cbc5c2 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.RemoteAnimationAdapter; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The controller for all operations related to locktask. */ private LockTaskController mLockTaskController; private ActivityStartController mActivityStartController; + PackageConfigPersister mPackageConfigPersister; boolean mSuppressResizeConfigChanges; @@ -866,6 +868,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { setRecentTasks(new RecentTasks(this, mTaskSupervisor)); mVrController = new VrController(mGlobalLock); mKeyguardController = mTaskSupervisor.getKeyguardController(); + mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue); } public void onActivityManagerInternalAdded() { @@ -2027,8 +2030,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** + * 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { mAppWarnings.onPackageUninstalled(name); mCompatModePackages.handlePackageUninstalledLocked(name); + mPackageConfigPersister.onPackageUninstall(name); } } @@ -6103,6 +6130,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void removeUser(int userId) { synchronized (mGlobalLock) { mRootWindowContainer.removeUser(userId); + mPackageConfigPersister.removeUser(userId); } } @@ -6200,6 +6228,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void loadRecentTasksForUser(int userId) { synchronized (mGlobalLock) { mRecentTasks.loadUserRecentsLocked(userId); + // TODO renaming the methods(?) + mPackageConfigPersister.loadUserPackages(userId); } } @@ -6308,5 +6338,54 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 d90e88576909..efcaaa42ec13 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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e51d690cabed..86968ed6d175 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -240,6 +240,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -663,12 +664,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private boolean mRemoved; - /** - * Non-null if the last size compatibility mode activity is using non-native screen - * configuration. The activity is not able to put in multi-window mode, so it exists only one - * per display. - */ - private ActivityRecord mLastCompatModeActivity; + /** Set of activities in foreground size compat mode. */ + private Set<ActivityRecord> mActiveSizeCompatActivities = new ArraySet<>(); // Used in updating the display size private Point mTmpDisplaySize = new Point(); @@ -5553,24 +5550,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** 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 000000000000..1552a96d699a --- /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 ceebe9550846..6fbeaa4e944e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2656,7 +2656,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 94e14dd0a6b8..ef4a40f4837c 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 @@ public class StartingSurfaceController { 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 @@ public class StartingSurfaceController { 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 9c8a997ec098..66d5c77a5c5e 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_RECENTS_ANIMA 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 @@ class Task extends WindowContainer<WindowContainer> { }); } + 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 @@ class Task extends WindowContainer<WindowContainer> { 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) { @@ -5844,12 +5855,8 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.acquireLaunchWakelock(); } - if (didAutoPip) { - // Already entered PIP mode, no need to keep pausing. - return true; - } - - if (mPausingActivity != null) { + // If already entered PIP mode, no need to keep pausing. + if (mPausingActivity != null && !didAutoPip) { // Have the window manager pause its key dispatching until the new // activity has started. If we're pausing the activity just because // the screen is being turned off and the UI is sleeping, don't interrupt @@ -5872,9 +5879,9 @@ class Task extends WindowContainer<WindowContainer> { } } else { - // This activity failed to schedule the - // pause, so just treat it as being paused now. - ProtoLog.v(WM_DEBUG_STATES, "Activity not running, resuming next."); + // This activity either failed to schedule the pause or it entered PIP mode, + // so just treat it as being paused now. + ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next."); if (resuming == null) { mRootWindowContainer.resumeFocusedTasksTopActivities(); } @@ -6558,10 +6565,9 @@ class Task extends WindowContainer<WindowContainer> { 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 +6590,7 @@ class Task extends WindowContainer<WindowContainer> { 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 +6751,10 @@ class Task extends WindowContainer<WindowContainer> { 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 9fac3f003f70..c0bce6be8c54 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { }); } + 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // 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/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 264a3b4edfa6..c3620235d3df 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 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } onConfigurationChanged(atm.getGlobalConfiguration()); + mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName); } public void setPid(int pid) { @@ -802,6 +803,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio 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 a7abf6a4fa99..11e3ecfbb90d 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -30,6 +30,7 @@ cc_library_static { "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 @@ cc_library_static { "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", diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 9a8942b47428..d076434e7d90 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 index ea5e7183c905..ea5e7183c905 100644 --- a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp +++ b/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 a6029cd28029..89b931d35c40 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,7 +73,8 @@ static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK)); static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) { - 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 index 5dbb71a4976c..a47ab9d27c17 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 @@ private: 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 @@ static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr 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 @@ static const JNINativeMethod method_table[] = { {"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 index 22950c5036e4..9924e24f14d0 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 34f604895721..1815f0cd44c9 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -39,8 +39,9 @@ int register_android_server_UsbMidiDevice(JNIEnv* env); 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_net_NetworkStatsService(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 e27e1b8ca89d..1406dbb12e02 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 08b82072747b..a5ccffcfbb2b 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 @@ package com.android.server.compat.overrides { 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 @@ package com.android.server.compat.overrides { 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 @@ package com.android.server.compat.overrides { 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 59b7367102c8..e76597d0a2f3 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { } 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 @@ class ActiveAdmin { } 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 @@ class ActiveAdmin { 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 b063e6727f5b..9772c2eaba57 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -214,6 +214,7 @@ import android.content.res.Resources; 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * however it's too early in the boot process to register with IIpConnectivityMetrics * to listen for events. */ - if (Intent.ACTION_USER_STARTED.equals(action) - && userHandle == mOwners.getDeviceOwnerUserId()) { + if (Intent.ACTION_USER_STARTED.equals(action) && userHandle == UserHandle.USER_SYSTEM) { synchronized (getLockObject()) { if (isNetworkLoggingEnabledInternalLocked()) { setNetworkLoggingActiveInternal(true); @@ -1357,6 +1357,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mContext.getSystemService(WifiManager.class); } + UsbManager getUsbManager() { + return mContext.getSystemService(UsbManager.class); + } + @SuppressWarnings("AndroidFrameworkBinderIdentity") long binderClearCallingIdentity() { return Binder.clearCallingIdentity(); @@ -2928,6 +2932,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { revertTransferOwnershipIfNecessaryLocked(); } + updateUsbDataSignal(); } private void revertTransferOwnershipIfNecessaryLocked() { @@ -6372,7 +6377,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist( + () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist( caller.getUserId())); } @@ -7527,6 +7532,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } + void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) { + if (userId == UserHandle.USER_ALL) { + userId = UserHandle.USER_SYSTEM; + } + ComponentName receiverComponent = null; + if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) { + receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, userId); + } + if (receiverComponent == null) { + receiverComponent = getOwnerComponent(userId); + } + sendActiveAdminCommand(action, extras, userId, receiverComponent); + } + private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) { sendActiveAdminCommand(action, extras, userId, mOwners.getProfileOwnerComponent(userId)); @@ -8351,6 +8370,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { deleteTransferOwnershipBundleLocked(userId); toggleBackupServiceActive(userId, true); applyManagedProfileRestrictionIfDeviceOwnerLocked(); + setNetworkLoggingActiveInternal(false); } @Override @@ -14131,7 +14151,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); synchronized (getLockObject()) { @@ -14139,11 +14161,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // already in the requested state return; } - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - deviceOwner.isNetworkLoggingEnabled = enabled; + final ActiveAdmin activeAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId()); + activeAdmin.isNetworkLoggingEnabled = enabled; if (!enabled) { - deviceOwner.numNetworkLoggingNotifications = 0; - deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + activeAdmin.numNetworkLoggingNotifications = 0; + activeAdmin.lastNetworkLoggingNotificationTimeMs = 0; } saveSettingsLocked(caller.getUserId()); setNetworkLoggingActiveInternal(enabled); @@ -14161,7 +14183,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { mInjector.binderWithCleanCallingIdentity(() -> { if (active) { - mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal()); + if (mNetworkLogger == null) { + final int affectedUserId = getNetworkLoggingAffectedUser(); + mNetworkLogger = new NetworkLogger(this, + mInjector.getPackageManagerInternal(), + affectedUserId == UserHandle.USER_SYSTEM + ? UserHandle.USER_ALL : affectedUserId); + } if (!mNetworkLogger.startNetworkLogging()) { mNetworkLogger = null; Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" @@ -14181,6 +14209,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private @UserIdInt int getNetworkLoggingAffectedUser() { + synchronized (getLockObject()) { + if (mOwners.hasDeviceOwner()) { + return mOwners.getDeviceOwnerUserId(); + } else { + return mInjector.binderWithCleanCallingIdentity( + () -> getManagedUserId(UserHandle.USER_SYSTEM)); + } + } + } + + private ActiveAdmin getNetworkLoggingControllingAdminLocked() { + int affectedUserId = getNetworkLoggingAffectedUser(); + if (affectedUserId < 0) { + return null; + } + return getDeviceOrProfileOwnerAdminLocked(affectedUserId); + } + @Override public long forceNetworkLogs() { Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), @@ -14201,10 +14248,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @GuardedBy("getLockObject()") private void maybePauseDeviceWideLoggingLocked() { if (!areAllUsersAffiliatedWithDeviceLocked()) { - Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be " - + "paused if enabled."); - if (mNetworkLogger != null) { - mNetworkLogger.pause(); + if (mOwners.hasDeviceOwner()) { + Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be " + + "paused if enabled."); + if (mNetworkLogger != null) { + mNetworkLogger.pause(); + } } if (!isOrganizationOwnedDeviceWithManagedProfile()) { Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be " @@ -14223,7 +14272,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (allUsersAffiliated || orgOwnedProfileDevice) { mSecurityLogMonitor.resume(); } - if (allUsersAffiliated) { + // If there is no device owner, then per-user network logging may be enabled for the + // managed profile. In which case, all users do not need to be affiliated. + if (allUsersAffiliated || !mOwners.hasDeviceOwner()) { if (mNetworkLogger != null) { mNetworkLogger.resume(); } @@ -14250,7 +14301,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)) || hasCallingOrSelfPermission(permission.MANAGE_USERS)); @@ -14260,8 +14313,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private boolean isNetworkLoggingEnabledInternalLocked() { - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled; + ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked(); + return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled; } /* @@ -14278,9 +14331,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); - checkAllUsersAreAffiliatedWithDevice(); + if (mOwners.hasDeviceOwner()) { + checkAllUsersAreAffiliatedWithDevice(); + } synchronized (getLockObject()) { if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) { @@ -14293,34 +14350,35 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); final long currentTime = System.currentTimeMillis(); - DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); + DevicePolicyData policyData = getUserData(caller.getUserId()); if (currentTime > policyData.mLastNetworkLogsRetrievalTime) { policyData.mLastNetworkLogsRetrievalTime = currentTime; - saveSettingsLocked(UserHandle.USER_SYSTEM); + saveSettingsLocked(caller.getUserId()); } return mNetworkLogger.retrieveLogs(batchToken); } } private void sendNetworkLoggingNotificationLocked() { - final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) { + ensureLocked(); + final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked(); + if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) { return; } - if (deviceOwner.numNetworkLoggingNotifications >= - ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + if (activeAdmin.numNetworkLoggingNotifications + >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { return; } final long now = System.currentTimeMillis(); - if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { + if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { return; } - deviceOwner.numNetworkLoggingNotifications++; - if (deviceOwner.numNetworkLoggingNotifications + activeAdmin.numNetworkLoggingNotifications++; + if (activeAdmin.numNetworkLoggingNotifications >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { - deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + activeAdmin.lastNetworkLoggingNotificationTimeMs = 0; } else { - deviceOwner.lastNetworkLoggingNotificationTimeMs = now; + activeAdmin.lastNetworkLoggingNotificationTimeMs = now; } final PackageManagerInternal pm = mInjector.getPackageManagerInternal(); final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); @@ -14340,7 +14398,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .bigText(mContext.getString(R.string.network_logging_notification_text))) .build(); mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification); - saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier()); } /** @@ -14404,8 +14462,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getLastNetworkLogRetrievalTime() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); - return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime; + + Preconditions.checkCallAuthorization(isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())) + || canManageUsers(caller)); + final int affectedUserId = getNetworkLoggingAffectedUser(); + return affectedUserId >= 0 ? getUserData(affectedUserId).mLastNetworkLogsRetrievalTime : -1; } @Override @@ -16567,4 +16629,78 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mPolicyCache.canAdminGrantSensorsPermissionsForUser(userId); } + + @Override + public void setUsbDataSignalingEnabled(String packageName, boolean enabled) { + Objects.requireNonNull(packageName, "Admin package name must be provided"); + final CallerIdentity caller = getCallerIdentity(packageName); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "USB data signaling can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + Preconditions.checkState(canUsbDataSignalingBeDisabled(), + "USB data signaling cannot be disabled."); + + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (admin.mUsbDataSignalingEnabled != enabled) { + admin.mUsbDataSignalingEnabled = enabled; + saveSettingsLocked(caller.getUserId()); + updateUsbDataSignal(); + } + } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING) + .setAdmin(packageName) + .setBoolean(enabled) + .write(); + } + + private void updateUsbDataSignal() { + if (!canUsbDataSignalingBeDisabled()) { + return; + } + final boolean usbEnabled; + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + usbEnabled = admin != null && admin.mUsbDataSignalingEnabled; + } + if (!mInjector.binderWithCleanCallingIdentity( + () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) { + Slog.w(LOG_TAG, "Failed to set usb data signaling state"); + } + } + + @Override + public boolean isUsbDataSignalingEnabled(String packageName) { + final CallerIdentity caller = getCallerIdentity(packageName); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "USB data signaling can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + return admin.mUsbDataSignalingEnabled; + } + } + + @Override + public boolean isUsbDataSignalingEnabledForUser(int userId) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(isSystemUid(caller)); + + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return admin != null && admin.mUsbDataSignalingEnabled; + } + } + + @Override + public boolean canUsbDataSignalingBeDisabled() { + return mInjector.binderWithCleanCallingIdentity(() -> + mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index e9b2d7f56108..8843a5d5306e 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.Bundle; 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 @@ final class NetworkLogger { 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 @@ final class NetworkLogger { 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 @@ final class NetworkLogger { 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 @@ final class NetworkLogger { 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 @@ final class NetworkLogger { /* allowIo */ false); mHandlerThread.start(); mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(), - mDpm); + mDpm, mTargetUserId); mNetworkLoggingHandler.scheduleBatchFinalization(); mIsLoggingEnabled.set(true); return true; @@ -153,7 +174,7 @@ final class NetworkLogger { } /** - * 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 @@ final class NetworkLogger { } /** - * 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 0a7070ffe4f6..84e89a08e1f4 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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { private final AlarmManager mAlarmManager; private long mId; + private int mTargetUserId; private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() { @Override @@ -82,10 +83,10 @@ final class NetworkLoggingHandler extends Handler { + 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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { @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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { + ", 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 @@ final class NetworkLoggingHandler extends Handler { } @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 @@ final class NetworkLoggingHandler extends Handler { 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 @@ final class NetworkLoggingHandler extends Handler { } @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 @@ final class NetworkLoggingHandler extends Handler { return extras; } - /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may - call into NetworkLoggingHandler. */ - private void notifyDeviceOwner(Bundle extras) { - Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " - + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1)); + /** Sends a notification to the device owner or profile owner. Should not hold locks as + DevicePolicyManagerService may call into NetworkLoggingHandler. */ + private void notifyDeviceOwnerOrProfileOwner(Bundle extras) { if (Thread.holdsLock(this)) { Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held"); return; } - mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + Slog.d(TAG, "Sending network logging batch broadcast to device owner or profile owner, " + + "batchToken: " + + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1)); + mDpm.sendDeviceOwnerOrProfileOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, + extras, mTargetUserId); } synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) { diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java index 1801f3bca30e..a2768c637d79 100644 --- a/services/java/com/android/server/SystemConfigService.java +++ b/services/java/com/android/server/SystemConfigService.java @@ -21,6 +21,10 @@ import static java.util.stream.Collectors.toMap; 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 @@ public class SystemConfigService extends SystemService { 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 a0e5c5deef61..2b09d12c97c7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -138,6 +138,7 @@ import com.android.server.integrity.AppIntegrityManagerService; import com.android.server.lights.LightsService; import com.android.server.location.LocationManagerService; import com.android.server.media.MediaRouterService; +import com.android.server.media.metrics.MediaMetricsManagerService; import com.android.server.media.projection.MediaProjectionManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; @@ -195,6 +196,7 @@ import com.android.server.twilight.TwilightService; 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; @@ -252,6 +254,10 @@ public final class SystemServer implements Dumpable { "com.android.server.companion.CompanionDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; + private static final String SCHEDULING_APEX_PATH = + "/apex/com.android.scheduling/javalib/service-scheduling.jar"; + private static final String REBOOT_READINESS_LIFECYCLE_CLASS = + "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = @@ -1295,6 +1301,7 @@ public final class SystemServer implements Dumpable { IStorageManager storageManager = null; NetworkManagementService networkManagement = null; IpSecService ipSecService = null; + VpnManagerService vpnManager = null; VcnManagementService vcnManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -1883,6 +1890,15 @@ public final class SystemServer implements Dumpable { 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); @@ -2381,6 +2397,10 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartPeopleService"); mSystemServiceManager.startService(PeopleService.class); t.traceEnd(); + + t.traceBegin("StartMediaMetricsManager"); + mSystemServiceManager.startService(MediaMetricsManagerService.class); + t.traceEnd(); } if (!isWatch) { @@ -2436,6 +2456,12 @@ public final class SystemServer implements Dumpable { STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH); t.traceEnd(); + // Reboot Readiness + t.traceBegin("StartRebootReadinessManagerService"); + mSystemServiceManager.startServiceFromJar( + REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH); + t.traceEnd(); + // Statsd pulled atoms t.traceBegin("StartStatsPullAtomService"); mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS); @@ -2611,6 +2637,7 @@ public final class SystemServer implements Dumpable { 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) @@ -2725,6 +2752,15 @@ public final class SystemServer implements Dumpable { 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 c2e0b776fc60..2d23fb4990bf 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 @@ class DomainVerificationEnforcerTest { 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 a76152c9df7d..a92ab9e35ddc 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 @@ class DomainVerificationPersistenceTest { 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 @@ class DomainVerificationPersistenceTest { 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 @@ class DomainVerificationPersistenceTest { > <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 @@ class DomainVerificationPersistenceTest { 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 5629d1c1107d..48518f4693dd 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 @@ class DomainVerificationSettingsMutationTest { 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 000000000000..d2ab6462d96b --- /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 000000000000..b1d921aa9db1 --- /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 2822d5c69091..1f102ac32a8e 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 @@ public class TestInjector implements Injector { 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 @@ public class TestInjector implements Injector { 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 @@ public class TestInjector implements Injector { } @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 000000000000..c3cca64154d6 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.provider; + +import static com.android.server.location.LocationUtils.createLocation; +import static com.android.server.location.LocationUtils.createLocationResult; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.location.Location; +import android.location.LocationResult; +import android.location.provider.ProviderRequest; +import android.platform.test.annotations.Presubmit; +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.location.eventlog.LocationEventLog; +import com.android.server.location.injector.TestInjector; +import com.android.server.location.test.FakeProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.Random; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class StationaryThrottlingLocationProviderTest { + + private static final String TAG = "StationaryThrottlingLocationProviderTest"; + + private Random mRandom; + private TestInjector mInjector; + private FakeProvider mDelegateProvider; + + private @Mock AbstractLocationProvider.Listener mListener; + private @Mock FakeProvider.FakeProviderInterface mDelegate; + + private StationaryThrottlingLocationProvider mProvider; + + @Before + public void setUp() { + initMocks(this); + + long seed = System.currentTimeMillis(); + Log.i(TAG, "location random seed: " + seed); + + mRandom = new Random(seed); + + mInjector = new TestInjector(); + mDelegateProvider = new FakeProvider(mDelegate); + + mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector, + mDelegateProvider, new LocationEventLog()); + mProvider.getController().setListener(mListener); + mProvider.getController().start(); + } + + @After + public void tearDown() { + mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST); + mProvider.getController().stop(); + } + + @Test + public void testThrottle() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); + verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); + verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(false); + verify(mDelegate, times(2)).onSetRequest(request); + verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class)); + } + + @Test + public void testThrottle_NoInitialLocation() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + + mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom)); + verify(mListener, times(1)).onReportLocation(any(LocationResult.class)); + verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(false); + verify(mDelegate, times(2)).onSetRequest(request); + verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class)); + } + + @Test + public void testNoThrottle_noLocation() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + verify(mListener, never()).onReportLocation(any(LocationResult.class)); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, after(75).times(0)).onReportLocation(any(LocationResult.class)); + } + + @Test + public void testNoThrottle_oldLocation() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + Location l = createLocation("test_provider", mRandom); + l.setElapsedRealtimeNanos(0); + + LocationResult loc = LocationResult.wrap(l); + mDelegateProvider.reportLocation(loc); + verify(mListener, times(1)).onReportLocation(loc); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); + } + + @Test + public void testNoThrottle_locationSettingsIgnored() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis( + 50).setLocationSettingsIgnored(true).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + LocationResult loc = createLocationResult("test_provider", mRandom); + mDelegateProvider.reportLocation(loc); + verify(mListener, times(1)).onReportLocation(loc); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS index 6561778cb47d..f1402ead3866 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 *AppOp* = file:/core/java/android/permission/OWNERS 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/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java index 9b6c7234981c..73b0105210c4 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.GenericDocument; 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 @@ public class AppSearchImplTest { @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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { /*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 @@ public class AppSearchImplTest { final List<AppSearchSchema> finalSchemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build()); - SetSchemaResult setSchemaResult = + SetSchemaResponse setSchemaResponse = mAppSearchImpl.setSchema( "package", "database1", @@ -846,7 +852,7 @@ public class AppSearchImplTest { /*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 @@ public class AppSearchImplTest { 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 d0767ccb6f87..c165c661a625 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 @@ class ApplicationInfoBuilder { private boolean mIsDebuggable; private int mTargetSdk; private String mPackageName; + private long mVersionCode; private ApplicationInfoBuilder() { mTargetSdk = -1; @@ -46,6 +47,11 @@ class ApplicationInfoBuilder { return this; } + ApplicationInfoBuilder withVersionCode(Long versionCode) { + mVersionCode = versionCode; + return this; + } + ApplicationInfo build() { final ApplicationInfo applicationInfo = new ApplicationInfo(); if (mIsDebuggable) { @@ -53,6 +59,7 @@ class ApplicationInfoBuilder { } 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 a53ff9bc7fdc..8b0e948579fb 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 @@ package com.android.server.compat; 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.mockito.Mockito.when; 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 android.content.pm.PackageManager.NameNotFoundException; 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.IOException; 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 @@ public class CompatConfigTest { 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 @@ public class CompatConfigTest { 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 @@ public class CompatConfigTest { 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 @@ public class CompatConfigTest { 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 @@ public class CompatConfigTest { } @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 @@ public class CompatConfigTest { 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 @@ public class CompatConfigTest { .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 @@ public class CompatConfigTest { .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 @@ public class CompatConfigTest { + " <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 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 testLoadOverrides() throws Exception { + 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 a1b2dc8bd82d..799b06734b54 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 @@ public class PlatformCompatTest { 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 @@ public class PlatformCompatTest { 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 class PlatformCompatTest { 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 @@ public class PlatformCompatTest { 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 @@ public class PlatformCompatTest { } @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 @@ public class PlatformCompatTest { 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 @@ public class PlatformCompatTest { 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 @@ public class PlatformCompatTest { 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 class PlatformCompatTest { 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 5b0704d69a68..61d7ede98f45 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.Intent; 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @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 7597cbf322f5..5bb65abd3033 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.ResolveInfo; 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 944ef8db4e0d..f6dee385ca07 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.PackageManager; 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 class MockSystemServices { 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 @@ public class MockSystemServices { 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 7506dd45ad82..743b25f5c2b4 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 @@ public class NetworkEventTest extends DpmTestBase { private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception { // GIVEN a handler with events NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(), - mDpmTestable, startingId); + mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE); // GIVEN network events are sent to the handler. for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo", diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 23a4c2f417c5..732c08c98f68 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.SensorEventListener; 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.Mock; 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 f0b4f1bec77b..9396ed256409 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.Resources; 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 org.junit.runner.RunWith; 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 640d6e599736..1c55072cab2c 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.display.VirtualDisplayConfig; 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.ArgumentCaptor; 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 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { assertNotNull(internalViewport); assertNotNull(virtualViewport); - // INTERNAL - assertTrue(internalViewport.valid); - // VIRTUAL assertEquals(height, virtualViewport.deviceHeight); assertEquals(width, virtualViewport.deviceWidth); @@ -243,10 +250,12 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive actual viewports: 1 internal - assertEquals(1, viewports.size()); - - DisplayViewport internalViewport = viewports.get(0); - - // INTERNAL is the only one actual display. - assertNotNull(internalViewport); - assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type); - assertTrue(internalViewport.valid); - assertEquals(displayId, internalViewport.displayId); + // 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); + + // 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); + assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0); + } } @Test @@ -486,7 +501,6 @@ public class DisplayManagerServiceTest { * 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 @@ public class DisplayManagerServiceTest { // 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 2e3178b13892..ee0f2e8c6f6a 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.ContentResolver; 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.concurrent.TimeUnit; 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 @@ public class DisplayModeDirectorTest { @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 ac4501723c90..ece0a627f051 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.junit.Assert.assertEquals; 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 @@ public class LogicalDisplayTest { 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 196454bd32ce..d72606ac44e5 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.assertNull; 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.io.OutputStream; import java.nio.charset.StandardCharsets; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class PersistentDataStoreTest { private PersistentDataStore mDataStore; diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 4bbf96fcb7ef..7771afc8c7f1 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -684,8 +684,8 @@ public final class UpdatableFontDirTest { newAddFontFamilyRequest("<family lang='en'>" + " <font>test.ttf</font>" + "</family>"))); - fail("Expect IllegalArgumentException"); - } catch (IllegalArgumentException e) { + fail("Expect NullPointerException"); + } catch (NullPointerException e) { // Expect } } @@ -703,9 +703,10 @@ public final class UpdatableFontDirTest { dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>" + " <font>test.ttf</font>" + "</family>"))); - fail("Expect IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expect + fail("Expect SystemFontException"); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 9f0d9829df01..5342486f930b 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.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; @SmallTest @Presubmit @@ -1586,4 +1588,46 @@ public class HdmiCecLocalDevicePlaybackTest { 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 0f527f3713b2..4623eb5b7d4b 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.HdmiControlManager; 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 @@ public class HdmiCecLocalDeviceTvTest { private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; + @Mock private AudioManager mAudioManager; @Before public void setUp() { @@ -101,11 +106,21 @@ public class HdmiCecLocalDeviceTvTest { } @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 @@ public class HdmiCecLocalDeviceTvTest { 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 @@ public class HdmiCecLocalDeviceTvTest { 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/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index f9b25d9342d3..f87d5993c1b5 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.annotation.NonNull; import android.util.Pair; import android.util.SparseIntArray; +import androidx.test.filters.LargeTest; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; @@ -496,6 +497,7 @@ public class WorkCountTrackerTest { } @Test + @LargeTest public void testRandom13() { assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES); @@ -508,9 +510,9 @@ public class WorkCountTrackerTest { Pair.create(WORK_TYPE_BGUSER, 3)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); - final double probStop = 0.01; - final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9); - final double probStart = 0.99; + final double probStop = 0.13; + final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85); + final double probStart = 0.87; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, EQUAL_PROBABILITY_CDF, numTypesCdf, probStop); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java index 32445fd1a47d..2eedc3251daa 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 @@ package com.android.server.locksettings; 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 @@ public class RebootEscrowDataTest { 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 @@ public class RebootEscrowDataTest { assertThat(decrypted, is(testSp)); } + @Test + public void fromEncryptedData_legacyVersion_success() throws Exception { + byte[] testSp = getTestSp(); + byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp); + + // Write a legacy blob encrypted only by k_s. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(1); + dos.writeByte(3); + dos.write(ksEncryptedBlob); + byte[] legacyBlob = bos.toByteArray(); + + RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null); + + assertThat(actual.getSpVersion(), is((byte) 3)); + assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes())); + assertThat(actual.getSyntheticPassword(), is(testSp)); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index 0dcd608e6bbd..a1b2f38af473 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 androidx.test.runner.AndroidJUnit4; 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.FileOutputStream; 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 @@ import java.util.zip.ZipOutputStream; 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 @@ public class DexMetadataHelperTest { } 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { 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 @@ public class DexMetadataHelperTest { try (FileInputStream is = new FileInputStream(base)) { final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite( ParseTypeImpl.forDefaultParsing().reset(), is.getFD(), - base.getAbsolutePath(), /* flags */ 0); + base.getAbsolutePath(), /*flags=*/0); if (result.isError()) { throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), result.getException()); diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS new file mode 100644 index 000000000000..81b6f05a1658 --- /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 5f64249213ec..22c38c1961b8 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 @@ package com.android.server.rotationresolver; 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 @@ import org.mockito.MockitoAnnotations; */ @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/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java index 72c40ea5a2be..014bfd2d40e4 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 android.os.Vibrator; 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 f562c1613413..4634e12f1530 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 @@ import java.util.Map; * 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 e71c2f5ba8da..8c62b7fe235e 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 @@ public class InputDeviceDelegateTest { 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 @@ public class InputDeviceDelegateTest { 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 @@ public class InputDeviceDelegateTest { 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 @@ public class InputDeviceDelegateTest { 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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { @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 @@ public class InputDeviceDelegateTest { @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 class InputDeviceDelegateTest { 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 class InputDeviceDelegateTest { 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 class InputDeviceDelegateTest { 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 3ff8e76a3707..1b7e1ca6a014 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.SystemClock; 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 @@ public class VibrationThreadTest { 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 index da3d1d6187fc..ba0a472c80dd 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 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 com.android.server.LocalServices; import org.junit.After; import org.junit.Before; @@ -533,28 +531,15 @@ public class VibratorManagerServiceTest { 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/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index be489c3bcd12..5614aa2a165d 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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "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/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 72b84396e985..aa1110cd55a7 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 @@ public class ActivityRecordTests extends WindowTestsBase { 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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index cc4d4eaa9e8b..e843dd71381f 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 class SizeCompatTests extends WindowTestsBase { 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 @@ public class SizeCompatTests extends WindowTestsBase { // 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 @@ public class SizeCompatTests extends WindowTestsBase { } @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 77fca3d2fdeb..9c1614393554 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 class WindowOrganizerTests extends WindowTestsBase { 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 class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void removeStartingWindow(int taskId) { } - + @Override + public void copySplashScreenView(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -829,6 +833,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 83b30a9747d7..c13d6b19bf1d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1144,6 +1144,9 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 aa8bbde8b32a..89e5d5f15242 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -19,6 +19,7 @@ java_library_static { "android.hardware.usb-V1.0-java", "android.hardware.usb-V1.1-java", "android.hardware.usb-V1.2-java", + "android.hardware.usb-V1.3-java", "android.hardware.usb.gadget-V1.0-java", "android.hardware.usb.gadget-V1.1-java", "android.hardware.usb.gadget-V1.2-java", diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index ca18c57d6f4b..6bf67154efd5 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.ParcelableUsbPort; 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 @@ public class UsbPortManager { */ private int mIsPortContaminatedNotificationId; + private boolean mEnableUsbDataSignaling; + protected int mCurrentUsbHalVersion; + public UsbPortManager(Context context) { mContext = context; try { @@ -181,6 +184,7 @@ public class UsbPortManager { if (mProxy != null) { try { mProxy.queryPortStatus(); + mEnableUsbDataSignaling = true; } catch (RemoteException e) { logAndPrintException(null, "ServiceStart: Failed to query port status", e); @@ -346,6 +350,66 @@ public class UsbPortManager { } } + /** + * 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 @@ public class UsbPortManager { 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 @@ public class UsbPortManager { 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 @@ public class UsbPortManager { 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 edd4a3874c94..9a13d7648be2 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 @@ public class UsbService extends IUsbManager.Stub { } @Override + public int getUsbHalVersion() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long ident = Binder.clearCallingIdentity(); + try { + if (mPortManager != null) { + return mPortManager.getUsbHalVersion(); + } else { + return UsbManager.USB_HAL_NOT_SUPPORTED; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public boolean enableUsbDataSignal(boolean enable) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long ident = Binder.clearCallingIdentity(); + try { + if (mPortManager != null) { + return mPortManager.enableUsbDataSignal(enable); + } else { + return false; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); synchronized (mLock) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 21cf3e57115d..3f6162d6422b 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5351,11 +5351,28 @@ public class CarrierConfigManager { 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/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index eb04f6907748..ac9e6817a230 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 @@ class PlatformCompatCommandNotInstalledTest { 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 401d87af5a33..0508125edfc8 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 @@ public class StagedRollbackTest { } /** - * 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 @@ public class StagedRollbackTest { 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 @@ public class StagedRollbackTest { } @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 1d5730fb4427..65fb7b6c8cc6 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { }); } + /** + * 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } } + 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 1b8fa672fe38..1f22cae8f3cf 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 fa2a60b21b50..cc31f2c98425 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 2201efd3a7ac..8dc53ac26715 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.Assert.fail; 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 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { // Test rollback-app command waits for staged sessions to be ready @Test + @LargeTest public void testAdbRollbackAppWaitsForStagedReady() throws Exception { assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); diff --git a/tests/UpdatableSystemFontTest/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING index a5c447934f43..7fbf426c71c4 100644 --- a/tests/UpdatableSystemFontTest/TEST_MAPPING +++ b/tests/UpdatableSystemFontTest/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "UpdatableSystemFontTest" } diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e1da3d0ae2b3..dc9e587332cb 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 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { 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 95a794235a2e..c548e30761c9 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -49,7 +49,7 @@ public class VpnManagerTest { 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 @@ public class VpnManagerTest { @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 @@ public class VpnManagerTest { 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 2fd5e3861cef..866f38c84333 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 class VpnTransportInfoTest { 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 e639a369eca2..2a693eb94015 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.ResolverParamsParcel; 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 @@ public class ConnectivityServiceTest { private MockContext mServiceContext; private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; private ConnectivityService.Dependencies mDeps; private ConnectivityService mService; private WrappedConnectivityManager mCm; @@ -390,6 +392,7 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, @@ -1425,6 +1461,8 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { .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 @@ public class ConnectivityServiceTest { .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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7458,6 +7528,8 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.removeCapability(testCap); callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has + // it. + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + } mCellNetworkAgent.removeCapability(testCap); callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); callbackWithoutCap.assertNoCallback(); - verify(mMockNetd).networkClearDefault(); + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkClearDefault(); + } mCm.unregisterNetworkCallback(callbackWithCap); mCm.unregisterNetworkCallback(callbackWithoutCap); diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 3556c72776dc..8f5ae97bc4c5 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -89,8 +89,8 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class PermissionMonitorTest { - private static final int MOCK_USER1 = 0; - private static final int MOCK_USER2 = 1; + private static final UserHandle MOCK_USER1 = UserHandle.of(0); + private static final UserHandle MOCK_USER2 = UserHandle.of(1); private static final int MOCK_UID1 = 10001; private static final int MOCK_UID2 = 10086; private static final int SYSTEM_UID1 = 1000; @@ -123,10 +123,7 @@ public class PermissionMonitorTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mUserManager.getUserHandles(eq(true))).thenReturn( - Arrays.asList(new UserHandle[] { - new UserHandle(MOCK_USER1), - new UserHandle(MOCK_USER2), - })); + Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 })); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); @@ -184,7 +181,8 @@ public class PermissionMonitorTest { return packageInfo; } - private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) { + private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, + UserHandle user) { final PackageInfo pkgInfo; if (hasSystemPermission) { pkgInfo = systemPackageInfoWithPermissions( @@ -192,7 +190,7 @@ public class PermissionMonitorTest { } else { pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, ""); } - pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid)); return pkgInfo; } @@ -382,8 +380,8 @@ public class PermissionMonitorTest { }).when(mockNetd).networkClearPermissionForUser(any(int[].class)); } - public void expectPermission(Boolean permission, int[] users, int[] apps) { - for (final int user : users) { + public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (!mApps.containsKey(uid)) { @@ -396,8 +394,8 @@ public class PermissionMonitorTest { } } - public void expectNoPermission(int[] users, int[] apps) { - for (final int user : users) { + public void expectNoPermission(UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (mApps.containsKey(uid)) { @@ -425,46 +423,48 @@ public class PermissionMonitorTest { // Add SYSTEM_PACKAGE2, expect only have network permission. mPermissionMonitor.onUserAdded(MOCK_USER1); - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); // Add SYSTEM_PACKAGE1, expect permission escalate. - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserAdded(MOCK_USER2); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); // Remove MOCK_UID1, expect no permission left for all user. mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); // Remove SYSTEM_PACKAGE1, expect permission downgrade. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2}); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserRemoved(MOCK_USER1); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID}); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID}); // Remove all packages, expect no permission left. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{}); - removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); // Remove last user, expect no redundant clearPermission is invoked. mPermissionMonitor.onUserRemoved(MOCK_USER2); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); } @@ -548,14 +548,14 @@ public class PermissionMonitorTest { // Normal package add/remove operations will trigger multiple intent for uids corresponding to // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be // called multiple times with the uid corresponding to each user. - private void addPackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void addPackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid)); } } - private void removePackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void removePackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid)); } } diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 1102624da031..4a1f96d145bd 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 @@ public class BroadcastInterceptingContext extends ContextWrapper { 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 BroadcastInterceptingContext extends ContextWrapper { 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 class BroadcastInterceptingContext extends ContextWrapper { } 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; } - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + /** + * 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) { - mInterceptors.add(new BroadcastInterceptor(receiver, filter)); + mUseRegisteredHandlers = use; } - return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + 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 278d93a1b17b..e7154802f1f2 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.doReturn; 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 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testEnterStateDoesNotCancelSafemodeAlarm() { + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + } + + @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() @@ -81,6 +88,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -120,7 +128,24 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testMigratedTransformsAreApplied() throws Exception { + getChildSessionCallback() + .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); + mTestLooper.dispatchAll(); + + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { + verify(mIpSecSvc) + .applyTunnelModeTransform( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); + } + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test public void testChildOpenedRegistersNetwork() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + final VcnChildSessionConfiguration mMockChildSessionConfig = mock(VcnChildSessionConfiguration.class); doReturn(Collections.singletonList(TEST_INTERNAL_ADDR)) @@ -162,22 +187,41 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection 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 d936183e5a0b..07282c920088 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 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).kill(); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -73,6 +74,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); verify(mIkeSession, never()).kill(); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -92,6 +94,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -101,5 +104,11 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio 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 16181b6f839a..49ce54d4f684 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -48,7 +48,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect .createIpSecTunnelInterface( DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); mGatewayConnection.setTunnelInterface(tunnelIface); - mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); + + // Don't need to transition to DisconnectedState because it is the starting state mTestLooper.dispatchAll(); } @@ -78,6 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -88,6 +90,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -97,5 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect 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 d0fec55a6827..22eab2a1aa65 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.Before; 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 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec 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 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec 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 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec // 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 3f2b47cd58fd..6c2607586629 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 @@ import org.junit.runner.RunWith; @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 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } @Test @@ -56,6 +61,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */); } @Test @@ -66,13 +72,28 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect 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 bc6bee28d14f..748c7924685d 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 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { @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 d449eab494f6..ac9ec0663df2 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.UnderlyingNetworkTracker.UnderlyingNetworkR 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.ipsec.ike.IkeSessionCallback; 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 org.mockito.ArgumentCaptor; 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 @@ public class VcnGatewayConnectionTestBase { 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 @@ public class VcnGatewayConnectionTestBase { @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 @@ public class VcnGatewayConnectionTestBase { 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 @@ public class VcnGatewayConnectionTestBase { 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 @@ public class VcnGatewayConnectionTestBase { 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()); + } } |