diff options
923 files changed, 28444 insertions, 13164 deletions
diff --git a/Android.bp b/Android.bp index e4c5c37ab576..908280e5d7f3 100644 --- a/Android.bp +++ b/Android.bp @@ -371,7 +371,6 @@ filegroup { srcs: [ // Java/AIDL sources under frameworks/base ":framework-blobstore-sources", - ":framework-connectivity-sources", // framework-connectivity is not yet a module ":framework-core-sources", ":framework-drm-sources", ":framework-graphics-nonupdatable-sources", @@ -437,6 +436,7 @@ filegroup { name: "framework-updatable-sources", srcs: [ ":framework-appsearch-sources", + ":framework-connectivity-sources", ":framework-graphics-srcs", ":framework-mediaprovider-sources", ":framework-permission-sources", @@ -639,6 +639,7 @@ java_defaults { defaults: ["framework-aidl-export-defaults"], srcs: [ ":framework-non-updatable-sources", + ":framework-connectivity-sources", "core/java/**/*.logtags", ], // See comment on framework-atb-backward-compatibility module below @@ -700,6 +701,8 @@ java_library { apex_available: ["//apex_available:platform"], visibility: [ "//frameworks/base", + // TODO: remove when framework-connectivity can build against API + "//frameworks/base/packages/Connectivity/framework", // TODO(b/147128803) remove the below lines "//frameworks/base/apex/appsearch/framework", "//frameworks/base/apex/blobstore/framework", @@ -1452,6 +1455,7 @@ java_library { ], libs: [ "framework-annotations-lib", + "framework-connectivity", "unsupportedappusage", ], visibility: [ diff --git a/StubLibraries.bp b/StubLibraries.bp index 4bd524f229ca..bff222e7d42f 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -58,6 +58,9 @@ stubs_defaults { local_include_dirs: [ "apex/media/aidl/stable", "media/aidl", + // TODO: move to include-dirs for packages/modules/Connectivity when this moves out of + // frameworks/base + "packages/Connectivity/framework/aidl-export", "telephony/java", ], include_dirs: ["frameworks/av/aidl"], @@ -310,6 +313,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs", + "framework-connectivity.stubs", "framework-graphics.stubs", "framework-media.stubs", "framework-mediaprovider.stubs", @@ -334,6 +338,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs.system", + "framework-connectivity.stubs.system", "framework-graphics.stubs.system", "framework-media.stubs.system", "framework-mediaprovider.stubs.system", @@ -374,6 +379,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs.system", + "framework-connectivity.stubs.system", "framework-graphics.stubs.system", "framework-media.stubs.system", "framework-mediaprovider.stubs.system", diff --git a/apct-tests/perftests/contentcapture/Android.bp b/apct-tests/perftests/contentcapture/Android.bp index 19a66ad9f27b..638403d40ea3 100644 --- a/apct-tests/perftests/contentcapture/Android.bp +++ b/apct-tests/perftests/contentcapture/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "ContentCapturePerfTests", srcs: ["src/**/*.java"], diff --git a/apct-tests/perftests/inputmethod/Android.bp b/apct-tests/perftests/inputmethod/Android.bp index 463ac9b8b0c8..f2f1f758112e 100644 --- a/apct-tests/perftests/inputmethod/Android.bp +++ b/apct-tests/perftests/inputmethod/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "ImePerfTests", srcs: ["src/**/*.java"], diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp index ab44dd92b657..87f65a922004 100644 --- a/apex/appsearch/Android.bp +++ b/apex/appsearch/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + apex { name: "com.android.appsearch", manifest: "apex_manifest.json", diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp index 8ba79541088b..5def55fd31ff 100644 --- a/apex/appsearch/framework/Android.bp +++ b/apex/appsearch/framework/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-appsearch-sources", srcs: [ diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index 905000ac7c3d..168c7c2f13cd 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -2,6 +2,7 @@ package android.app.appsearch { public final class AppSearchBatchResult<KeyType, ValueType> { + method @NonNull public java.util.Map<KeyType,android.app.appsearch.AppSearchResult<ValueType>> getAll(); method @NonNull public java.util.Map<KeyType,android.app.appsearch.AppSearchResult<ValueType>> getFailures(); method @NonNull public java.util.Map<KeyType,ValueType> getSuccesses(); method public boolean isSuccess(); @@ -146,8 +147,8 @@ package android.app.appsearch { method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); - method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); - method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); + method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); + method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); } @@ -216,7 +217,7 @@ package android.app.appsearch { public class GlobalSearchSession implements java.io.Closeable { method public void close(); - method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); + method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); } public class PackageIdentifier { @@ -286,7 +287,7 @@ package android.app.appsearch { public class SearchResults implements java.io.Closeable { method public void close(); - method public void getNextPage(@NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.List<android.app.appsearch.SearchResult>>>); + method public void getNextPage(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.List<android.app.appsearch.SearchResult>>>); } public final class SearchSpec { @@ -358,7 +359,6 @@ package android.app.appsearch { method @NonNull public java.util.Set<java.lang.String> getIncompatibleTypes(); method @NonNull public java.util.Set<java.lang.String> getMigratedTypes(); method @NonNull public java.util.List<android.app.appsearch.SetSchemaResponse.MigrationFailure> getMigrationFailures(); - method public boolean isSuccess(); } public static class SetSchemaResponse.MigrationFailure { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java index cd75b1456ba8..31ab259127c3 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -28,11 +28,19 @@ import java.util.Collections; import java.util.Map; /** - * Provides access to multiple {@link AppSearchResult}s from a batch operation accepting multiple - * inputs. + * Provides results for AppSearch batch operations which encompass multiple documents. * - * @param <KeyType> The type of the keys for {@link #getSuccesses} and {@link #getFailures}. - * @param <ValueType> The type of result objects associated with the keys. + * <p>Individual results of a batch operation are separated into two maps: one for successes and one + * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of + * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link + * AppSearchResult} objects. + * + * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for + * both successes and failures. + * + * @see AppSearchSession#put + * @see AppSearchSession#getByUri + * @see AppSearchSession#remove */ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { @NonNull private final Map<KeyType, ValueType> mSuccesses; @@ -75,8 +83,11 @@ 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 keys mapped to instances of the value type for all successful + * individual results. + * + * <p>Example: {@link AppSearchSession#getByUri} returns an {@link AppSearchBatchResult}. Each + * key (a URI of {@code String} type) will map to a {@link GenericDocument} object. * * <p>The values of the {@link Map} will not be {@code null}. */ @@ -86,8 +97,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Returns a {@link Map} of all failed keys mapped to the failed {@link AppSearchResult}s they - * produced. + * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed + * individual results. * * <p>The values of the {@link Map} will not be {@code null}. */ @@ -97,10 +108,10 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Returns a {@link Map} of all keys mapped to the {@link AppSearchResult}s they produced. + * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all + * individual results. * * <p>The values of the {@link Map} will not be {@code null}. - * @hide */ @NonNull public Map<KeyType, AppSearchResult<ValueType>> getAll() { @@ -150,8 +161,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl /** * Builder for {@link AppSearchBatchResult} objects. * - * @param <KeyType> The type of keys. - * @param <ValueType> The type of result objects associated with the keys. + * <p>Once {@link #build} is called, the instance can no longer be used. + * * @hide */ public static final class Builder<KeyType, ValueType> { @@ -161,9 +172,11 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl private boolean mBuilt = false; /** - * Associates the {@code key} with the given successful return value. + * Associates the {@code key} with the provided successful return value. * * <p>Any previous mapping for a key, whether success or failure, is deleted. + * + * @throws IllegalStateException if the builder has already been used. */ @NonNull public Builder<KeyType, ValueType> setSuccess( @@ -174,9 +187,11 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Associates the {@code key} with the given failure code and error message. + * Associates the {@code key} with the provided failure code and error message. * * <p>Any previous mapping for a key, whether success or failure, is deleted. + * + * @throws IllegalStateException if the builder has already been used. */ @NonNull public Builder<KeyType, ValueType> setFailure( @@ -189,9 +204,11 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Associates the {@code key} with the given {@code result}. + * Associates the {@code key} with the provided {@code result}. * * <p>Any previous mapping for a key, whether success or failure, is deleted. + * + * @throws IllegalStateException if the builder has already been used. */ @NonNull public Builder<KeyType, ValueType> setResult( @@ -210,7 +227,11 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl return this; } - /** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */ + /** + * Builds an {@link AppSearchBatchResult} object from the contents of this {@link Builder}. + * + * @throws IllegalStateException if the builder has already been used. + */ @NonNull public AppSearchBatchResult<KeyType, ValueType> build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 69d4e536597f..6a5975ef7ff7 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -36,12 +36,89 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * This class provides access to the centralized AppSearch index maintained by the system. + * Provides access to the centralized AppSearch index maintained by the system. * - * <p>Apps can index structured text documents with AppSearch, which can then be retrieved through - * the query API. + * <p>AppSearch is a search library for managing structured data featuring: + * <ul> + * <li>A fully offline on-device solution + * <li>A set of APIs for applications to index documents and retrieve them via full-text search + * <li>APIs for applications to allow the System to display their content on system UI surfaces + * <li>Similarly, APIs for applications to allow the System to share their content with other + * specified applications. + * </ul> + * + * <p>Applications create a database by opening an {@link AppSearchSession}. + * + * <p>Example: + * <pre> + * AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); + * + * AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder(). + * setDatabaseName(dbName).build()); + * appSearchManager.createSearchSession(searchContext, mExecutor, appSearchSessionResult -> { + * mAppSearchSession = appSearchSessionResult.getResultValue(); + * });</pre> + * + * <p>After opening the session, a schema must be set in order to define the organizational + * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema + * is composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique + * type of data. + * + * <p>Example: + * <pre> + * AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email") + * .addProperty(new StringPropertyConfig.Builder("subject") + * .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + * .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + * .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + * .build() + * ).build(); + * + * SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(emailSchemaType).build(); + * mAppSearchSession.set(request, mExecutor, appSearchResult -> { + * if (appSearchResult.isSuccess()) { + * //Schema has been successfully set. + * } + * });</pre> + * + * <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object, + * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a + * logical group of documents. For example, a namespace can be created to group documents on a + * per-account basis. A URI identifies a single document within a namespace. The combination + * of URI and namespace uniquely identifies a {@link GenericDocument} in the database. + * + * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database + * and indexed by calling {@link AppSearchSession#put}. + * + * <p>Example: + * <pre> + * // Although for this example we use GenericDocument directly, we recommend extending + * // GenericDocument to create specific types (i.e. Email) with specific setters/getters. + * GenericDocument email = new GenericDocument.Builder<>(URI, EMAIL_SCHEMA_TYPE) + * .setNamespace(NAMESPACE) + * .setPropertyString(“subject”, EMAIL_SUBJECT) + * .setScore(EMAIL_SCORE) + * .build(); + * + * PutDocumentsRequest request = new PutDocumentsRequest.Builder().addGenericDocuments(email) + * .build(); + * mAppSearchSession.put(request, mExecutor, appSearchBatchResult -> { + * if (appSearchBatchResult.isSuccess()) { + * //All documents have been successfully indexed. + * } + * });</pre> + * + * <p>Searching within the database is done by calling {@link AppSearchSession#search} and providing + * the query string to search for, as well as a {@link SearchSpec}. + * + * <p>Alternatively, {@link AppSearchSession#getByUri} can be called to retrieve documents by URI + * and namespace. + * + * <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove + * operation. Remove operations can be done by URI and namespace via + * {@link AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)}, + * or by query via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}. */ -// TODO(b/148046169): This class header needs a detailed example/tutorial. @SystemService(Context.APP_SEARCH_SERVICE) public class AppSearchManager { /** diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 73ca0ccab46d..24cc60e5eef5 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -41,7 +41,7 @@ import java.util.function.Consumer; * Represents a connection to an AppSearch storage system where {@link GenericDocument}s can be * placed and queried. * - * This class is thread safe. + * <p>This class is thread safe. */ public final class AppSearchSession implements Closeable { private static final String TAG = "AppSearchSession"; @@ -102,92 +102,23 @@ public final class AppSearchSession implements Closeable { } /** - * Sets the schema that will be used by documents provided to the {@link #put} method. - * - * <p>The schema provided here is compared to the stored copy of the schema previously supplied - * 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. - * </ul> - * - * <p>The following types of schema changes are not backwards-compatible: - * - * <ul> - * <li>Removal of an existing type - * <li>Removal of a property from a type - * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property - * <li>For properties of {@code Document} type, changing the schema type of {@code Document}s - * of that property - * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link - * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link - * AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property). - * <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property. - * </ul> - * - * <p>Supplying a schema with such changes will, by default, result in this call 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 {@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. + * Sets the schema that represents the organizational structure of data within the AppSearch + * database. * - * <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. + * <p>Upon creating an {@link AppSearchSession}, {@link #setSchema} should be called. If the + * schema needs to be updated, or it has not been previously set, then the provided schema will + * be saved and persisted to disk. Otherwise, {@link #setSchema} is handled efficiently as a + * no-op call. * - * @param request The schema update request. + * @param request the schema to set or update the AppSearch database to. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive errors resulting from setting the schema. If the * operation succeeds, the callback will be invoked with {@code null}. * @see android.app.appsearch.AppSearchSchema.Migrator * @see android.app.appsearch.AppSearchMigrationHelper.Transformer */ + // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are + // exposed. public void setSchema( @NonNull SetSchemaRequest request, @NonNull @CallbackExecutor Executor executor, @@ -280,12 +211,13 @@ public final class AppSearchSession implements Closeable { } /** - * Indexes documents into AppSearch. + * Indexes documents into the {@link AppSearchSession} database. * - * <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. + * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an {@link + * AppSearchSchema} type that has been previously registered by calling the {@link #setSchema} + * method. * - * @param request {@link PutDocumentsRequest} containing documents to be indexed + * @param request 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 @@ -463,21 +395,15 @@ public final class AppSearchSession implements Closeable { * @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 a {@link SearchResults} object for retrieved matched documents. */ @NonNull - public SearchResults search( - @NonNull String queryExpression, - @NonNull SearchSpec searchSpec, - @NonNull @CallbackExecutor Executor executor) { + public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { Objects.requireNonNull(queryExpression); Objects.requireNonNull(searchSpec); - Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); return new SearchResults(mService, mPackageName, mDatabaseName, queryExpression, - searchSpec, mUserId, executor); + searchSpec, mUserId); } /** @@ -497,7 +423,6 @@ public final class AppSearchSession implements Closeable { * @param callback Callback to receive errors. If the operation succeeds, the callback will be * invoked with {@code null}. */ - @NonNull public void reportUsage( @NonNull ReportUsageRequest request, @NonNull @CallbackExecutor Executor executor, diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index 09bca4fb3b9d..8dd9dc1be312 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -98,8 +98,7 @@ public class GlobalSearchSession implements Closeable { * <p>Document access can also be granted to system UIs by specifying {@link * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} when building a schema. * - * <p>See {@link AppSearchSession#search} for a detailed explanation on - * forming a query string. + * <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}. @@ -107,21 +106,15 @@ public class GlobalSearchSession implements Closeable { * @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 a {@link SearchResults} object for retrieved matched documents. */ @NonNull - public SearchResults search( - @NonNull String queryExpression, - @NonNull SearchSpec searchSpec, - @NonNull @CallbackExecutor Executor executor) { + public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { Objects.requireNonNull(queryExpression); Objects.requireNonNull(searchSpec); - Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed"); return new SearchResults(mService, mPackageName, /*databaseName=*/null, queryExpression, - searchSpec, mUserId, executor); + searchSpec, mUserId); } /** Closes the {@link GlobalSearchSession}. */ diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index a63e01555f6c..531c98425288 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -33,12 +33,16 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * SearchResults are a returned object from a query API. + * Encapsulates results of a search operation. * - * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based - * on request. + * <p>Each {@link AppSearchSession#search} operation returns a list of {@link SearchResult} objects, + * referred to as a "page", limited by the size configured by {@link + * SearchSpec.Builder#setResultCountPerPage}. * - * <p>Should close this object after finish fetching results. + * <p>To fetch a page of results, call {@link #getNextPage}. + * + * <p>All instances of {@link SearchResults} must call {@link SearchResults#close()} after the + * results are fetched. * * <p>This class is not thread safe. */ @@ -61,8 +65,6 @@ public class SearchResults implements Closeable { @UserIdInt private final int mUserId; - private final Executor mExecutor; - private long mNextPageToken; private boolean mIsFirstLoad = true; @@ -75,28 +77,31 @@ public class SearchResults implements Closeable { @Nullable String databaseName, @NonNull String queryExpression, @NonNull SearchSpec searchSpec, - @UserIdInt int userId, - @NonNull @CallbackExecutor Executor executor) { + @UserIdInt int userId) { mService = Objects.requireNonNull(service); mPackageName = packageName; mDatabaseName = databaseName; mQueryExpression = Objects.requireNonNull(queryExpression); mSearchSpec = Objects.requireNonNull(searchSpec); mUserId = userId; - mExecutor = Objects.requireNonNull(executor); } /** - * Gets a whole page of {@link SearchResult}s. + * Retrieves the next page of {@link SearchResult} objects. * - * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty - * list. + * <p>The page size is configured by {@link SearchSpec.Builder#setResultCountPerPage}. * - * <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}. + * <p>Continue calling this method to access results until it returns an empty list, signifying + * there are no more results. * + * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. */ - public void getNextPage(@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { + public void getNextPage( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "SearchResults has already been closed"); try { if (mIsFirstLoad) { @@ -104,14 +109,14 @@ public class SearchResults implements Closeable { if (mDatabaseName == null) { // Global query, there's no one package-database combination to check. mService.globalQuery(mPackageName, mQueryExpression, - mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); + mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback)); } else { // Normal local query, pass in specified database. mService.query(mPackageName, mDatabaseName, mQueryExpression, - mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); + mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback)); } } else { - mService.getNextPage(mNextPageToken, mUserId, wrapCallback(callback)); + mService.getNextPage(mNextPageToken, mUserId, wrapCallback(executor, callback)); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -131,10 +136,11 @@ public class SearchResults implements Closeable { } private IAppSearchResultCallback wrapCallback( + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { return new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { - mExecutor.execute(() -> invokeCallback(result, callback)); + executor.execute(() -> invokeCallback(result, callback)); } }; } 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 138eb23148af..72bb9f3d07c8 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java @@ -583,9 +583,9 @@ public class 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}. + * 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) { diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java index 05b212880962..01473be062bc 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java @@ -28,9 +28,9 @@ import java.util.Collections; import java.util.List; /** - * Encapsulates a request to index a document into an {@link AppSearchSession} database. + * Encapsulates a request to index documents into an {@link AppSearchSession} database. * - * <p>@see AppSearchSession#putDocuments + * @see AppSearchSession#put */ public final class PutDocumentsRequest { private final List<GenericDocument> mDocuments; @@ -39,7 +39,7 @@ public final class PutDocumentsRequest { mDocuments = documents; } - /** Returns the documents that are part of this request. */ + /** Returns a list of {@link GenericDocument} objects that are part of this request. */ @NonNull public List<GenericDocument> getGenericDocuments() { return Collections.unmodifiableList(mDocuments); @@ -54,14 +54,22 @@ public final class PutDocumentsRequest { private final List<GenericDocument> mDocuments = new ArrayList<>(); private boolean mBuilt = false; - /** Adds one or more {@link GenericDocument} objects to the request. */ + /** + * Adds one or more {@link GenericDocument} objects to the request. + * + * @throws IllegalStateException if the builder has already been used. + */ @NonNull public Builder addGenericDocuments(@NonNull GenericDocument... documents) { Preconditions.checkNotNull(documents); return addGenericDocuments(Arrays.asList(documents)); } - /** Adds a collection of {@link GenericDocument} objects to the request. */ + /** + * Adds a collection of {@link GenericDocument} objects to the request. + * + * @throws IllegalStateException if the builder has already been used. + */ @NonNull public Builder addGenericDocuments( @NonNull Collection<? extends GenericDocument> documents) { @@ -71,7 +79,11 @@ public final class PutDocumentsRequest { return this; } - /** Creates a new {@link PutDocumentsRequest} object. */ + /** + * Creates a new {@link PutDocumentsRequest} object. + * + * @throws IllegalStateException if the builder has already been used. + */ @NonNull public PutDocumentsRequest build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java index 2caa94a5ef07..426a903981b3 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java @@ -32,6 +32,41 @@ import java.util.Set; /** * Encapsulates a request to update the schema of an {@link AppSearchSession} database. * + * <p>The schema is composed of a collection of {@link AppSearchSchema} objects, each of which + * defines a unique type of data. + * + * <p>The first call to SetSchemaRequest will set the provided schema and store it within the {@link + * AppSearchSession} database. + * + * <p>Subsequent calls will compare the provided schema to the previously saved schema, to determine + * how to treat existing documents. + * + * <p>The following types of schema modifications are always safe and are made without deleting any + * existing documents: + * + * <ul> + * <li>Addition of new {@link AppSearchSchema} types + * <li>Addition of new properties to an existing {@link AppSearchSchema} type + * <li>Changing the cardinality of a property to be less restrictive + * </ul> + * + * <p>The following types of schema changes are not backwards compatible: + * + * <ul> + * <li>Removal of an existing {@link AppSearchSchema} type + * <li>Removal of a property from an existing {@link AppSearchSchema} type + * <li>Changing the data type of an existing property + * <li>Changing the cardinality of a property to be more restrictive + * </ul> + * + * <p>Providing a schema with incompatible changes, will throw an {@link + * android.app.appsearch.exceptions.AppSearchException}, with a message describing the + * incompatibility. As a result, the previously set schema will remain unchanged. + * + * <p>Backward incompatible changes can be made by setting {@link + * SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This deletes all documents + * that are incompatible with the new schema. The new schema is then saved and persisted to disk. + * * @see AppSearchSession#setSchema */ public final class SetSchemaRequest { @@ -54,14 +89,15 @@ public final class SetSchemaRequest { mForceOverride = forceOverride; } - /** Returns the schemas that are part of this request. */ + /** Returns the {@link AppSearchSchema} types that are part of this request. */ @NonNull public Set<AppSearchSchema> getSchemas() { return Collections.unmodifiableSet(mSchemas); } /** - * Returns the set of schema types that have opted out of being visible on system UI surfaces. + * Returns all the schema types that are opted out of being displayed and visible on any system + * UI surface. */ @NonNull public Set<String> getSchemasNotVisibleToSystemUi() { @@ -70,10 +106,9 @@ public final class SetSchemaRequest { /** * Returns a mapping of schema types to the set of packages that have access to that schema - * type. Each package is represented by a {@link PackageIdentifier}. name and byte[] - * certificate. + * type. * - * <p>This method is inefficient to call repeatedly. + * <p>It’s inefficient to call this method repeatedly. */ @NonNull public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() { @@ -91,9 +126,8 @@ public final class SetSchemaRequest { } /** - * Returns a mapping of schema types to the set of packages that have access to that schema - * type. Each package is represented by a {@link PackageIdentifier}. name and byte[] - * certificate. + * Returns a mapping of {@link AppSearchSchema} types to the set of packages that have access to + * that schema type. * * <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a * modifiable map. This is not meant to be unhidden and should only be used by internal classes. @@ -110,7 +144,11 @@ public final class SetSchemaRequest { return mForceOverride; } - /** Builder for {@link SetSchemaRequest} objects. */ + /** + * Builder for {@link SetSchemaRequest} objects. + * + * <p>Once {@link #build} is called, the instance can no longer be used. + */ public static final class Builder { private final Set<AppSearchSchema> mSchemas = new ArraySet<>(); private final Set<String> mSchemasNotVisibleToSystemUi = new ArraySet<>(); @@ -121,9 +159,11 @@ public final class SetSchemaRequest { private boolean mBuilt = false; /** - * Adds one or more types to the schema. + * Adds one or more {@link AppSearchSchema} types to the schema. + * + * <p>An {@link AppSearchSchema} object represents one type of structured data. * - * <p>Any documents of these types will be visible on system UI surfaces by default. + * @throws IllegalStateException if the builder has already been used. */ @NonNull public Builder addSchemas(@NonNull AppSearchSchema... schemas) { @@ -132,9 +172,11 @@ public final class SetSchemaRequest { } /** - * Adds one or more types to the schema. + * Adds a collection of {@link AppSearchSchema} objects to the schema. * - * <p>Any documents of these types will be visible on system UI surfaces by default. + * <p>An {@link AppSearchSchema} object represents one type of structured data. + * + * @throws IllegalStateException if the builder has already been used. */ @NonNull public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) { @@ -145,10 +187,17 @@ public final class SetSchemaRequest { } /** - * Sets visibility on system UI surfaces for the given {@code schemaType}. + * Sets whether or not documents from the provided {@code schemaType} will be displayed and + * visible on any system UI surface. + * + * <p>This setting applies to the provided {@code schemaType} only, and does not persist + * across {@link AppSearchSession#setSchema} calls. + * + * <p>By default, documents are displayed and visible on system UI surfaces. * * @param schemaType The schema type to set visibility on. * @param visible Whether the {@code schemaType} will be visible or not. + * @throws IllegalStateException if the builder has already been used. */ // Merged list available from getSchemasNotVisibleToSystemUi @SuppressLint("MissingGetterMatchingBuilder") @@ -167,11 +216,25 @@ public final class SetSchemaRequest { } /** - * Sets visibility for a package for the given {@code schemaType}. + * Sets whether or not documents from the provided {@code schemaType} can be read by the + * specified package. + * + * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name + * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. + * + * <p>To opt into one-way data sharing with another application, the developer will need to + * explicitly grant the other application’s package name and certificate Read access to its + * data. + * + * <p>For two-way data sharing, both applications need to explicitly grant Read access to + * one another. + * + * <p>By default, data sharing between applications is disabled. * * @param schemaType The schema type to set visibility on. * @param visible Whether the {@code schemaType} will be visible or not. * @param packageIdentifier Represents the package that will be granted visibility. + * @throws IllegalStateException if the builder has already been used. */ // Merged list available from getSchemasVisibleToPackages @SuppressLint("MissingGetterMatchingBuilder") @@ -224,13 +287,15 @@ public final class SetSchemaRequest { } /** - * Configures the {@link SetSchemaRequest} to delete any existing documents that don't - * follow the new schema. + * Sets whether or not to override the current schema in the {@link AppSearchSession} + * database. * - * <p>By default, this is {@code false} and schema incompatibility causes the {@link - * AppSearchSession#setSchema} call to fail. + * <p>Call this method whenever backward incompatible changes need to be made by setting + * {@code forceOverride} to {@code true}. As a result, during execution of the setSchema + * operation, all documents that are incompatible with the new schema will be deleted and + * the new schema will be saved and persisted. * - * @see AppSearchSession#setSchema + * <p>By default, this is {@code false}. */ @NonNull public Builder setForceOverride(boolean forceOverride) { @@ -239,10 +304,11 @@ public final class SetSchemaRequest { } /** - * Builds a new {@link SetSchemaRequest}. + * Builds a new {@link SetSchemaRequest} object. * - * @throws IllegalArgumentException If schema types were referenced, but the corresponding - * {@link AppSearchSchema} was never added. + * @throws IllegalArgumentException if schema types were referenced, but the corresponding + * {@link AppSearchSchema} type was never added. + * @throws IllegalStateException if the builder has already been used. */ @NonNull public SetSchemaRequest build() { 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 bc99d4f67d86..a146006f355c 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java @@ -79,12 +79,6 @@ public class SetSchemaResponse { return mBundle; } - /** TODO(b/177266929): Remove this deprecated method */ - //@Deprecated - public boolean isSuccess() { - return true; - } - /** * Returns a {@link List} of all failed {@link MigrationFailure}. * diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index 2fd5c733473e..57ee1ec482d8 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -11,6 +11,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_library { name: "service-appsearch", installable: true, diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index babcd25e3e26..7c92456bea49 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -218,9 +218,23 @@ public class VisibilityStore { * @throws AppSearchException AppSearchException on AppSearchImpl error. */ public void initialize() throws AppSearchException { - if (!mAppSearchImpl.hasSchemaTypeLocked(PACKAGE_NAME, DATABASE_NAME, VISIBILITY_TYPE) - || !mAppSearchImpl.hasSchemaTypeLocked( - PACKAGE_NAME, DATABASE_NAME, PACKAGE_ACCESSIBLE_TYPE)) { + List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME); + boolean hasVisibilityType = false; + boolean hasPackageAccessibleType = false; + for (int i = 0; i < schemas.size(); i++) { + AppSearchSchema schema = schemas.get(i); + if (schema.getSchemaType().equals(VISIBILITY_TYPE)) { + hasVisibilityType = true; + } else if (schema.getSchemaType().equals(PACKAGE_ACCESSIBLE_TYPE)) { + hasPackageAccessibleType = true; + } + + if (hasVisibilityType && hasPackageAccessibleType) { + // Found both our types, can exit early. + break; + } + } + if (!hasVisibilityType || !hasPackageAccessibleType) { // Schema type doesn't exist yet. Add it. mAppSearchImpl.setSchema( PACKAGE_NAME, @@ -250,10 +264,11 @@ public class VisibilityStore { /*typePropertyPaths=*/ Collections.emptyMap()); // Update platform visibility settings - String[] schemas = + String[] notPlatformSurfaceableSchemas = document.getPropertyStringArray(NOT_PLATFORM_SURFACEABLE_PROPERTY); - if (schemas != null) { - mNotPlatformSurfaceableMap.put(prefix, new ArraySet<>(Arrays.asList(schemas))); + if (notPlatformSurfaceableSchemas != null) { + mNotPlatformSurfaceableMap.put( + prefix, new ArraySet<>(Arrays.asList(notPlatformSurfaceableSchemas))); } // Update 3p package visibility settings @@ -333,7 +348,7 @@ public class VisibilityStore { schemasPackageAccessible.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { GenericDocument packageAccessibleDocument = - new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE) + new GenericDocument.Builder(/*uri=*/ "", PACKAGE_ACCESSIBLE_TYPE) .setNamespace(NAMESPACE) .setPropertyString( PACKAGE_NAME_PROPERTY, diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 6c2e30e98877..e2c211b7d303 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 @@ -654,6 +654,35 @@ public final class AppSearchImpl implements Closeable { } } + /** + * Returns a mapping of package names to all the databases owned by that package. + * + * <p>This method is inefficient to call repeatedly. + */ + @NonNull + public Map<String, Set<String>> getPackageToDatabases() { + mReadWriteLock.readLock().lock(); + try { + Map<String, Set<String>> packageToDatabases = new ArrayMap<>(); + for (String prefix : mSchemaMapLocked.keySet()) { + String packageName = getPackageName(prefix); + + Set<String> databases = packageToDatabases.get(packageName); + if (databases == null) { + databases = new ArraySet<>(); + packageToDatabases.put(packageName, databases); + } + + String databaseName = getDatabaseName(prefix); + databases.add(databaseName); + } + + return packageToDatabases; + } finally { + mReadWriteLock.readLock().unlock(); + } + } + @GuardedBy("mReadWriteLock") private SearchResultPage doQueryLocked( @NonNull Set<String> prefixes, @@ -1211,26 +1240,8 @@ public final class AppSearchImpl implements Closeable { return schemaProto.getSchema(); } - /** - * Returns true if the {@code packageName} and {@code databaseName} has the {@code schemaType} - */ - @GuardedBy("mReadWriteLock") - boolean hasSchemaTypeLocked( - @NonNull String packageName, @NonNull String databaseName, @NonNull String schemaType) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(schemaType); - - String prefix = createPrefix(packageName, databaseName); - Set<String> schemaTypes = mSchemaMapLocked.get(prefix); - if (schemaTypes == null) { - return false; - } - - return schemaTypes.contains(prefix + schemaType); - } - /** Returns a set of all prefixes AppSearchImpl knows about. */ + // TODO(b/180058203): Remove this method once platform has switched away from using this method. @GuardedBy("mReadWriteLock") @NonNull Set<String> getPrefixesLocked() { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java new file mode 100644 index 000000000000..0f23d926648b --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.external.localstorage; + +import android.annotation.NonNull; +import android.app.appsearch.exceptions.AppSearchException; + +import com.android.server.appsearch.external.localstorage.stats.CallStats; +import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats; + +/** + * An interface for implementing client-defined logging AppSearch operations stats. + * + * <p>Any implementation needs to provide general information on how to log all the stats types. + * (e.g. {@link CallStats}) + * + * <p>All implementations of this interface must be thread safe. + * + * @hide + */ +public interface AppSearchLogger { + /** Logs {@link CallStats} */ + void logStats(@NonNull CallStats stats) throws AppSearchException; + + /** Logs {@link PutDocumentStats} */ + void logStats(@NonNull PutDocumentStats stats) throws AppSearchException; + + // TODO(b/173532925) Add remaining logStats once we add all the stats. +} 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 a501e99db1ef..a7f1cc4c793f 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/177266929) change the output stream so that we can use it in platform + // TODO(b/151178558) change the output stream so that we can use it in platform CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream); SearchResultPage searchResultPage = mAppSearchImpl.query( diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java new file mode 100644 index 000000000000..81a5067c9fa1 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java @@ -0,0 +1,200 @@ +/* + * 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 com.android.server.appsearch.external.localstorage.stats; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A class for setting basic information to log for all function calls. + * + * <p>This class can set which stats to log for both batch and non-batch {@link + * android.app.appsearch.AppSearchSession} calls. + * + * <p>Some function calls like {@link android.app.appsearch.AppSearchSession#setSchema} have their + * own detailed stats class {@link placeholder}. However, {@link CallStats} can still be used along + * with the detailed stats class for easy aggregation/analysis with other function calls. + * + * @hide + */ +public class CallStats { + @IntDef( + value = { + CALL_TYPE_UNKNOWN, + CALL_TYPE_INITIALIZE, + CALL_TYPE_SET_SCHEMA, + CALL_TYPE_PUT_DOCUMENTS, + CALL_TYPE_GET_DOCUMENTS, + CALL_TYPE_REMOVE_DOCUMENTS, + CALL_TYPE_PUT_DOCUMENT, + CALL_TYPE_GET_DOCUMENT, + CALL_TYPE_REMOVE_DOCUMENT, + CALL_TYPE_QUERY, + CALL_TYPE_OPTIMIZE, + CALL_TYPE_FLUSH, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CallType {} + + public static final int CALL_TYPE_UNKNOWN = 0; + public static final int CALL_TYPE_INITIALIZE = 1; + public static final int CALL_TYPE_SET_SCHEMA = 2; + public static final int CALL_TYPE_PUT_DOCUMENTS = 3; + public static final int CALL_TYPE_GET_DOCUMENTS = 4; + public static final int CALL_TYPE_REMOVE_DOCUMENTS = 5; + public static final int CALL_TYPE_PUT_DOCUMENT = 6; + public static final int CALL_TYPE_GET_DOCUMENT = 7; + public static final int CALL_TYPE_REMOVE_DOCUMENT = 8; + public static final int CALL_TYPE_QUERY = 9; + public static final int CALL_TYPE_OPTIMIZE = 10; + public static final int CALL_TYPE_FLUSH = 11; + + @NonNull private final GeneralStats mGeneralStats; + @CallType private final int mCallType; + private final int mEstimatedBinderLatencyMillis; + private final int mNumOperationsSucceeded; + private final int mNumOperationsFailed; + + CallStats(@NonNull Builder builder) { + Preconditions.checkNotNull(builder); + mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStats); + mCallType = builder.mCallType; + mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis; + mNumOperationsSucceeded = builder.mNumOperationsSucceeded; + mNumOperationsFailed = builder.mNumOperationsFailed; + } + + /** Returns general information for the call. */ + @NonNull + public GeneralStats getGeneralStats() { + return mGeneralStats; + } + + /** Returns type of the call. */ + @CallType + public int getCallType() { + return mCallType; + } + + /** Returns estimated binder latency, in milliseconds */ + public int getEstimatedBinderLatencyMillis() { + return mEstimatedBinderLatencyMillis; + } + + /** + * Returns number of operations succeeded. + * + * <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total + * number of individual successful put operations. In this case, how many documents are + * successfully indexed. + * + * <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, the + * sum of {@link CallStats#getNumOperationsSucceeded()} and {@link + * CallStats#getNumOperationsFailed()} is always 1 since there is only one operation. + */ + public int getNumOperationsSucceeded() { + return mNumOperationsSucceeded; + } + + /** + * Returns number of operations failed. + * + * <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total + * number of individual failed put operations. In this case, how many documents are failed to be + * indexed. + * + * <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, the + * sum of {@link CallStats#getNumOperationsSucceeded()} and {@link + * CallStats#getNumOperationsFailed()} is always 1 since there is only one operation. + */ + public int getNumOperationsFailed() { + return mNumOperationsFailed; + } + + /** Builder for {@link CallStats}. */ + public static class Builder { + @NonNull final GeneralStats mGeneralStats; + @CallType int mCallType; + int mEstimatedBinderLatencyMillis; + int mNumOperationsSucceeded; + int mNumOperationsFailed; + + /** Builder takes {@link GeneralStats} to hold general stats. */ + public Builder(@NonNull GeneralStats generalStats) { + mGeneralStats = Preconditions.checkNotNull(generalStats); + } + + /** Sets type of the call. */ + @NonNull + public Builder setCallType(@CallType int callType) { + mCallType = callType; + return this; + } + + /** Sets estimated binder latency, in milliseconds. */ + @NonNull + public Builder setEstimatedBinderLatencyMillis(int estimatedBinderLatencyMillis) { + mEstimatedBinderLatencyMillis = estimatedBinderLatencyMillis; + return this; + } + + /** + * Sets number of operations succeeded. + * + * <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total + * number of individual successful put operations. In this case, how many documents are + * successfully indexed. + * + * <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, + * the sum of {@link CallStats#getNumOperationsSucceeded()} and {@link + * CallStats#getNumOperationsFailed()} is always 1 since there is only one operation. + */ + @NonNull + public Builder setNumOperationsSucceeded(int numOperationsSucceeded) { + mNumOperationsSucceeded = numOperationsSucceeded; + return this; + } + + /** + * Sets number of operations failed. + * + * <p>For example, for {@link android.app.appsearch.AppSearchSession#put}, it is the total + * number of individual failed put operations. In this case, how many documents are failed + * to be indexed. + * + * <p>For non-batch calls such as {@link android.app.appsearch.AppSearchSession#setSchema}, + * the sum of {@link CallStats#getNumOperationsSucceeded()} and {@link + * CallStats#getNumOperationsFailed()} is always 1 since there is only one operation. + */ + @NonNull + public Builder setNumOperationsFailed(int numOperationsFailed) { + mNumOperationsFailed = numOperationsFailed; + return this; + } + + /** Creates {@link CallStats} object from {@link Builder} instance. */ + @NonNull + public CallStats build() { + return new CallStats(/* builder= */ this); + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java new file mode 100644 index 000000000000..d2a45d5304f9 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java @@ -0,0 +1,122 @@ +/* + * 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 com.android.server.appsearch.external.localstorage.stats; + +import android.annotation.NonNull; +import android.app.appsearch.AppSearchResult; + +import com.android.internal.util.Preconditions; + +/** + * A class for holding general logging information. + * + * <p>This class cannot be logged by {@link + * com.android.server.appsearch.external.localstorage.AppSearchLogger} directly. It is used for + * defining general logging information that is shared across different stats classes. + * + * @see PutDocumentStats + * @see CallStats + * @hide + */ +public final class GeneralStats { + /** Package name of the application. */ + @NonNull private final String mPackageName; + + /** Database name within AppSearch. */ + @NonNull private final String mDatabase; + + /** + * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal + * state. + */ + @AppSearchResult.ResultCode private final int mStatusCode; + + private final int mTotalLatencyMillis; + + GeneralStats(@NonNull Builder builder) { + Preconditions.checkNotNull(builder); + mPackageName = Preconditions.checkNotNull(builder.mPackageName); + mDatabase = Preconditions.checkNotNull(builder.mDatabase); + mStatusCode = builder.mStatusCode; + mTotalLatencyMillis = builder.mTotalLatencyMillis; + } + + /** Returns package name. */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** Returns database name. */ + @NonNull + public String getDatabase() { + return mDatabase; + } + + /** Returns result code from {@link AppSearchResult#getResultCode()} */ + @AppSearchResult.ResultCode + public int getStatusCode() { + return mStatusCode; + } + + /** Returns total latency, in milliseconds. */ + public int getTotalLatencyMillis() { + return mTotalLatencyMillis; + } + + /** Builder for {@link GeneralStats}. */ + public static class Builder { + @NonNull final String mPackageName; + @NonNull final String mDatabase; + @AppSearchResult.ResultCode int mStatusCode; + int mTotalLatencyMillis; + + /** + * Constructor + * + * @param packageName name of the package logging stats + * @param dataBase name of the database logging stats + */ + public Builder(@NonNull String packageName, @NonNull String dataBase) { + mPackageName = Preconditions.checkNotNull(packageName); + mDatabase = Preconditions.checkNotNull(dataBase); + } + + /** Sets status code returned from {@link AppSearchResult#getResultCode()} */ + @NonNull + public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) { + mStatusCode = statusCode; + return this; + } + + /** Sets total latency, in milliseconds. */ + @NonNull + public Builder setTotalLatencyMillis(int totalLatencyMillis) { + mTotalLatencyMillis = totalLatencyMillis; + return this; + } + + /** + * Creates a new {@link GeneralStats} object from the contents of this {@link Builder} + * instance. + */ + @NonNull + public GeneralStats build() { + return new GeneralStats(/* builder= */ this); + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java new file mode 100644 index 000000000000..b1b643b66859 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java @@ -0,0 +1,219 @@ +/* + * 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 com.android.server.appsearch.external.localstorage.stats; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +/** + * A class for holding detailed stats to log for each individual document put by a {@link + * android.app.appsearch.AppSearchSession#put} call. + * + * @hide + */ +public final class PutDocumentStats { + /** {@link GeneralStats} holds the general stats. */ + @NonNull private final GeneralStats mGeneralStats; + + /** Time used to generate a document proto from a Bundle. */ + private final int mGenerateDocumentProtoLatencyMillis; + + /** Time used to rewrite types and namespaces in the document. */ + private final int mRewriteDocumentTypesLatencyMillis; + + /** Overall time used for the native function call. */ + private final int mNativeLatencyMillis; + + /** Time used to store the document. */ + private final int mNativeDocumentStoreLatencyMillis; + + /** Time used to index the document. It doesn't include the time to merge indices. */ + private final int mNativeIndexLatencyMillis; + + /** Time used to merge the indices. */ + private final int mNativeIndexMergeLatencyMillis; + + /** Document size in bytes. */ + private final int mNativeDocumentSizeBytes; + + /** Number of tokens added to the index. */ + private final int mNativeNumTokensIndexed; + + /** Number of tokens clipped for exceeding the max number. */ + private final int mNativeNumTokensClipped; + + PutDocumentStats(@NonNull Builder builder) { + Preconditions.checkNotNull(builder); + mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStats); + mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis; + mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis; + mNativeLatencyMillis = builder.mNativeLatencyMillis; + mNativeDocumentStoreLatencyMillis = builder.mNativeDocumentStoreLatencyMillis; + mNativeIndexLatencyMillis = builder.mNativeIndexLatencyMillis; + mNativeIndexMergeLatencyMillis = builder.mNativeIndexMergeLatencyMillis; + mNativeDocumentSizeBytes = builder.mNativeDocumentSizeBytes; + mNativeNumTokensIndexed = builder.mNativeNumTokensIndexed; + mNativeNumTokensClipped = builder.mNativeNumTokensClipped; + } + + /** Returns the {@link GeneralStats} object attached to this instance. */ + @NonNull + public GeneralStats getGeneralStats() { + return mGeneralStats; + } + + /** Returns time spent on generating document proto, in milliseconds. */ + public int getGenerateDocumentProtoLatencyMillis() { + return mGenerateDocumentProtoLatencyMillis; + } + + /** Returns time spent on rewriting types and namespaces in document, in milliseconds. */ + public int getRewriteDocumentTypesLatencyMillis() { + return mRewriteDocumentTypesLatencyMillis; + } + + /** Returns time spent in native, in milliseconds. */ + public int getNativeLatencyMillis() { + return mNativeLatencyMillis; + } + + /** Returns time spent on document store, in milliseconds. */ + public int getNativeDocumentStoreLatencyMillis() { + return mNativeDocumentStoreLatencyMillis; + } + + /** Returns time spent on indexing, in milliseconds. */ + public int getNativeIndexLatencyMillis() { + return mNativeIndexLatencyMillis; + } + + /** Returns time spent on merging indices, in milliseconds. */ + public int getNativeIndexMergeLatencyMillis() { + return mNativeIndexMergeLatencyMillis; + } + + /** Returns document size, in bytes. */ + public int getNativeDocumentSizeBytes() { + return mNativeDocumentSizeBytes; + } + + /** Returns number of tokens indexed. */ + public int getNativeNumTokensIndexed() { + return mNativeNumTokensIndexed; + } + + /** Returns number of tokens clipped for exceeding the max number. */ + public int getNativeNumTokensClipped() { + return mNativeNumTokensClipped; + } + + /** Builder for {@link PutDocumentStats}. */ + public static class Builder { + @NonNull final GeneralStats mGeneralStats; + int mGenerateDocumentProtoLatencyMillis; + int mRewriteDocumentTypesLatencyMillis; + int mNativeLatencyMillis; + int mNativeDocumentStoreLatencyMillis; + int mNativeIndexLatencyMillis; + int mNativeIndexMergeLatencyMillis; + int mNativeDocumentSizeBytes; + int mNativeNumTokensIndexed; + int mNativeNumTokensClipped; + + /** Builder takes {@link GeneralStats} to hold general stats. */ + public Builder(@NonNull GeneralStats generalStats) { + mGeneralStats = Preconditions.checkNotNull(generalStats); + } + + /** Sets how much time we spend for generating document proto, in milliseconds. */ + @NonNull + public Builder setGenerateDocumentProtoLatencyMillis( + int generateDocumentProtoLatencyMillis) { + mGenerateDocumentProtoLatencyMillis = generateDocumentProtoLatencyMillis; + return this; + } + + /** + * Sets how much time we spend for rewriting types and namespaces in document, in + * milliseconds. + */ + @NonNull + public Builder setRewriteDocumentTypesLatencyMillis(int rewriteDocumentTypesLatencyMillis) { + mRewriteDocumentTypesLatencyMillis = rewriteDocumentTypesLatencyMillis; + return this; + } + + /** Sets the native latency, in milliseconds. */ + @NonNull + public Builder setNativeLatencyMillis(int nativeLatencyMillis) { + mNativeLatencyMillis = nativeLatencyMillis; + return this; + } + + /** Sets how much time we spend on document store, in milliseconds. */ + @NonNull + public Builder setNativeDocumentStoreLatencyMillis(int nativeDocumentStoreLatencyMillis) { + mNativeDocumentStoreLatencyMillis = nativeDocumentStoreLatencyMillis; + return this; + } + + /** Sets the native index latency, in milliseconds. */ + @NonNull + public Builder setNativeIndexLatencyMillis(int nativeIndexLatencyMillis) { + mNativeIndexLatencyMillis = nativeIndexLatencyMillis; + return this; + } + + /** Sets how much time we spend on merging indices, in milliseconds. */ + @NonNull + public Builder setNativeIndexMergeLatencyMillis(int nativeIndexMergeLatencyMillis) { + mNativeIndexMergeLatencyMillis = nativeIndexMergeLatencyMillis; + return this; + } + + /** Sets document size, in bytes. */ + @NonNull + public Builder setNativeDocumentSizeBytes(int nativeDocumentSizeBytes) { + mNativeDocumentSizeBytes = nativeDocumentSizeBytes; + return this; + } + + /** Sets number of tokens indexed in native. */ + @NonNull + public Builder setNativeNumTokensIndexed(int nativeNumTokensIndexed) { + mNativeNumTokensIndexed = nativeNumTokensIndexed; + return this; + } + + /** Sets number of tokens clipped for exceeding the max number. */ + @NonNull + public Builder setNativeNumTokensClipped(int nativeNumTokensClipped) { + mNativeNumTokensClipped = nativeNumTokensClipped; + return this; + } + + /** + * Creates a new {@link PutDocumentStats} object from the contents of this {@link Builder} + * instance. + */ + @NonNull + public PutDocumentStats build() { + return new PutDocumentStats(/* builder= */ this); + } + } +} diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index d076db3b8f82..2c9477a5d76b 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -Ia9a8daef1a6d7d9432f7808d440abd64f4797701 +I593dfd22279739e5f578e07d36a55cf02ee942c5 diff --git a/apex/appsearch/testing/Android.bp b/apex/appsearch/testing/Android.bp index 54d50395e3bd..eb072afec696 100644 --- a/apex/appsearch/testing/Android.bp +++ b/apex/appsearch/testing/Android.bp @@ -11,6 +11,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_library { name: "AppSearchTestUtils", srcs: ["java/**/*.java"], diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index afa633a48b2b..9ef6e0b2dee8 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -124,8 +124,7 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @NonNull public SearchResultsShim search( @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { - SearchResults searchResults = - mAppSearchSession.search(queryExpression, searchSpec, mExecutor); + SearchResults searchResults = mAppSearchSession.search(queryExpression, searchSpec); return new SearchResultsShimImpl(searchResults, mExecutor); } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 6595d8d4abba..69a4c18c4028 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -75,8 +75,7 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { @Override public SearchResultsShim search( @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { - SearchResults searchResults = - mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor); + SearchResults searchResults = mGlobalSearchSession.search(queryExpression, searchSpec); return new SearchResultsShimImpl(searchResults, mExecutor); } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java index 75add81c8d64..5f26e8cba585 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java @@ -47,7 +47,7 @@ public class SearchResultsShimImpl implements SearchResultsShim { @NonNull public ListenableFuture<List<SearchResult>> getNextPage() { SettableFuture<AppSearchResult<List<SearchResult>>> future = SettableFuture.create(); - mSearchResults.getNextPage(future::set); + mSearchResults.getNextPage(mExecutor, future::set); return Futures.transform(future, AppSearchResult::getResultValue, mExecutor); } 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 ea21e19b2bea..1428fb1d3c7a 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 @@ -33,91 +33,19 @@ import java.util.Set; public interface AppSearchSessionShim extends Closeable { /** - * Sets the schema that will be used by documents provided to the {@link #put} method. + * Sets the schema that represents the organizational structure of data within the AppSearch + * database. * - * <p>The schema provided here is compared to the stored copy of the schema previously supplied - * 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: + * <p>Upon creating an {@link AppSearchSessionShim}, {@link #setSchema} should be called. If the + * schema needs to be updated, or it has not been previously set, then the provided schema will + * be saved and persisted to disk. Otherwise, {@link #setSchema} is handled efficiently as a + * no-op call. * - * <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. - * </ul> - * - * <p>The following types of schema changes are not backwards-compatible: - * - * <ul> - * <li>Removal of an existing type - * <li>Removal of a property from a type - * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property - * <li>For properties of {@code Document} type, changing the schema type of {@code Document}s - * of that property - * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link - * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link - * AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property). - * <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property. - * </ul> - * - * <p>Supplying a schema with such changes will, by default, result in this call 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>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. - * @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 + * @param request the schema to set or update the AppSearch database to. + * @return a {@link ListenableFuture} which resolves to a {@link SetSchemaResponse} object. */ + // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are + // exposed. @NonNull ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request); @@ -132,15 +60,17 @@ public interface AppSearchSessionShim extends Closeable { ListenableFuture<Set<AppSearchSchema>> getSchema(); /** - * Indexes documents into AppSearch. + * Indexes documents into the {@link AppSearchSessionShim} database. * - * <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. + * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an {@link + * AppSearchSchema} type that has been previously registered by calling the {@link #setSchema} + * method. * - * @param request {@link PutDocumentsRequest} containing documents to be indexed - * @return The pending result of performing this operation. The keys of the returned {@link - * AppSearchBatchResult} are the URIs of the input documents. The values are {@code null} if - * they were successfully indexed, or a failed {@link AppSearchResult} otherwise. + * @param request containing documents to be indexed. + * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The + * keys of the returned {@link AppSearchBatchResult} are the URIs of the input documents. + * The values are either {@code null} if the corresponding document was successfully + * indexed, or a failed {@link AppSearchResult} otherwise. */ @NonNull ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request); @@ -213,7 +143,7 @@ public interface AppSearchSessionShim extends Closeable { * 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()}. + * SearchResultsShim#getNextPage}. * * @param queryExpression query string to search. * @param searchSpec spec for setting document filters, adding projection, setting term match 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 31c934f8bb27..d912c08e7d5f 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 @@ -37,11 +37,11 @@ public interface GlobalSearchSessionShim extends Closeable { * 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>See {@link AppSearchSessionShim#search} for a detailed explanation on forming a query + * string. * * <p>This method is lightweight. The heavy work will be done in {@link - * SearchResultsShim#getNextPage()}. + * SearchResultsShim#getNextPage}. * * @param queryExpression query string to search. * @param searchSpec spec for setting document filters, adding projection, setting term match diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java index 328c65ca2727..38f61f83d24e 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java @@ -24,25 +24,29 @@ import java.io.Closeable; import java.util.List; /** - * SearchResultsShim are a returned object from a query API. + * Encapsulates results of a search operation. * - * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based - * on request. + * <p>Each {@link AppSearchSessionShim#search} operation returns a list of {@link SearchResult} + * objects, referred to as a "page", limited by the size configured by {@link + * SearchSpec.Builder#setResultCountPerPage}. * - * <p>Should close this object after finish fetching results. + * <p>To fetch a page of results, call {@link #getNextPage()}. + * + * <p>All instances of {@link SearchResultsShim} must call {@link SearchResultsShim#close()} after + * the results are fetched. * * <p>This class is not thread safe. */ public interface SearchResultsShim extends Closeable { /** - * Gets a whole page of {@link SearchResult}s. + * Retrieves the next page of {@link SearchResult} objects. * - * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty - * list. + * <p>The page size is configured by {@link SearchSpec.Builder#setResultCountPerPage}. * - * <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}. + * <p>Continue calling this method to access results until it returns an empty list, signifying + * there are no more results. * - * @return The pending result of performing this operation. + * @return a {@link ListenableFuture} which resolves to a list of {@link SearchResult} objects. */ @NonNull ListenableFuture<List<SearchResult>> getNextPage(); diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 7c7b21001c3b..77146e0d1282 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -23,7 +23,7 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -202,7 +202,7 @@ public class AlarmManager { * @hide */ @ChangeId - @Disabled // TODO (b/171306433): Enable starting S. + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L; @UnsupportedAppUsage diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl index 5693abe4d4e1..43d4873a3540 100644 --- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl +++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl @@ -43,10 +43,10 @@ interface IDeviceIdleController { boolean isPowerSaveWhitelistApp(String name); @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "Use SystemApi {@code PowerWhitelistManager#whitelistAppTemporarily(String, int, String)}.") - void addPowerSaveTempWhitelistApp(String name, long duration, int userId, String reason); - long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason); - long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason); - long whitelistAppTemporarily(String name, int userId, String reason); + void addPowerSaveTempWhitelistApp(String name, long duration, int userId, int reasonCode, String reason); + long addPowerSaveTempWhitelistAppForMms(String name, int userId, int reasonCode, String reason); + long addPowerSaveTempWhitelistAppForSms(String name, int userId, int reasonCode, String reason); + long whitelistAppTemporarily(String name, int userId, int reasonCode, String reason); void exitIdle(String reason); int setPreIdleTimeoutMode(int Mode); void resetPreIdleTimeoutMode(); diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index df0e157abc6a..df690d00a322 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -16,8 +16,16 @@ package android.os; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_TOP; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -94,6 +102,239 @@ public class PowerWhitelistManager { @Retention(RetentionPolicy.SOURCE) public @interface TempAllowListType {} + /* Reason code for BG-FGS-launch. */ + /** + * BG-FGS-launch is denied. + * @hide + */ + public static final int REASON_DENIED = -1; + /** + * The default reason code if reason is unknown. + */ + public static final int REASON_UNKNOWN = 0; + /** + * Use REASON_OTHER if there is no better choice. + */ + public static final int REASON_OTHER = 1; + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT = 10; + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT_UI = 11; + /** @hide */ + public static final int REASON_PROC_STATE_TOP = 12; + /** @hide */ + public static final int REASON_PROC_STATE_BTOP = 13; + /** @hide */ + public static final int REASON_PROC_STATE_FGS = 14; + /** @hide */ + public static final int REASON_PROC_STATE_BFGS = 15; + /** @hide */ + public static final int REASON_UID_VISIBLE = 50; + /** @hide */ + public static final int REASON_SYSTEM_UID = 51; + /** @hide */ + public static final int REASON_ACTIVITY_STARTER = 52; + /** @hide */ + public static final int REASON_START_ACTIVITY_FLAG = 53; + /** @hide */ + public static final int REASON_FGS_BINDING = 54; + /** @hide */ + public static final int REASON_DEVICE_OWNER = 55; + /** @hide */ + public static final int REASON_PROFILE_OWNER = 56; + /** @hide */ + public static final int REASON_COMPANION_DEVICE_MANAGER = 57; + /** + * START_ACTIVITIES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58; + /** + * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_FGS_PERMISSION = 59; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61; + /** @hide */ + public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62; + /** @hide */ + public static final int REASON_DEVICE_DEMO_MODE = 63; + /** @hide */ + public static final int REASON_EXEMPTED_PACKAGE = 64; + /** @hide */ + public static final int REASON_ALLOWLISTED_PACKAGE = 65; + /** + * If it's because of a role, + * @hide + */ + public static final int REASON_APPOP = 66; + + /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist. + Reason code for temp and system allowlist starts here. + */ + public static final int REASON_GEOFENCING = 100; + public static final int REASON_PUSH_MESSAGING = 101; + public static final int REASON_ACTIVITY_RECOGNITION = 102; + + /** + * Broadcast ACTION_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_BOOT_COMPLETED = 103; + /** + * Broadcast ACTION_PRE_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_PRE_BOOT_COMPLETED = 104; + + /** + * Broadcast ACTION_LOCKED_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_LOCKED_BOOT_COMPLETED = 105; + /** + * Device idle system allowlist, including EXCEPT-IDLE + * @hide + */ + public static final int REASON_SYSTEM_ALLOW_LISTED = 106; + /** @hide */ + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 107; + /** + * AlarmManagerService. + * @hide + */ + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 108; + /** + * ActiveServices. + * @hide + */ + public static final int REASON_SERVICE_LAUNCH = 109; + /** + * KeyChainSystemService. + * @hide + */ + public static final int REASON_KEY_CHAIN = 110; + /** + * PackageManagerService. + * @hide + */ + public static final int REASON_PACKAGE_VERIFIER = 111; + /** + * SyncManager. + * @hide + */ + public static final int REASON_SYNC_MANAGER = 112; + /** + * DomainVerificationProxyV1. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V1 = 113; + /** + * DomainVerificationProxyV2. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V2 = 114; + /** @hide */ + public static final int REASON_VPN = 115; + /** + * NotificationManagerService. + * @hide + */ + public static final int REASON_NOTIFICATION_SERVICE = 116; + /** + * Broadcast ACTION_MY_PACKAGE_REPLACED. + * @hide + */ + public static final int REASON_PACKAGE_REPLACED = 117; + /** + * LocationProviderManager. + * @hide + */ + public static final int REASON_LOCATION_PROVIDER = 118; + /** + * MediaButtonReceiver. + * @hide + */ + public static final int REASON_MEDIA_BUTTON = 119; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_SMS = 120; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_MMS = 121; + /** + * Shell app. + * @hide + */ + public static final int REASON_SHELL = 122; + + /** + * The list of BG-FGS-Launch and temp-allowlist reason code. + * @hide + */ + @IntDef(flag = true, prefix = { "REASON_" }, value = { + // BG-FGS-Launch reasons. + REASON_DENIED, + REASON_UNKNOWN, + REASON_OTHER, + REASON_PROC_STATE_PERSISTENT, + REASON_PROC_STATE_PERSISTENT_UI, + REASON_PROC_STATE_TOP, + REASON_PROC_STATE_BTOP, + REASON_PROC_STATE_FGS, + REASON_PROC_STATE_BFGS, + REASON_UID_VISIBLE, + REASON_SYSTEM_UID, + REASON_ACTIVITY_STARTER, + REASON_START_ACTIVITY_FLAG, + REASON_FGS_BINDING, + REASON_DEVICE_OWNER, + REASON_PROFILE_OWNER, + REASON_COMPANION_DEVICE_MANAGER, + REASON_BACKGROUND_ACTIVITY_PERMISSION, + REASON_BACKGROUND_FGS_PERMISSION, + REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION, + REASON_INSTR_BACKGROUND_FGS_PERMISSION, + REASON_SYSTEM_ALERT_WINDOW_PERMISSION, + REASON_DEVICE_DEMO_MODE, + REASON_EXEMPTED_PACKAGE, + REASON_ALLOWLISTED_PACKAGE, + REASON_APPOP, + // temp and system allowlist reasons. + REASON_GEOFENCING, + REASON_PUSH_MESSAGING, + REASON_ACTIVITY_RECOGNITION, + REASON_BOOT_COMPLETED, + REASON_PRE_BOOT_COMPLETED, + REASON_LOCKED_BOOT_COMPLETED, + REASON_SYSTEM_ALLOW_LISTED, + REASON_ALARM_MANAGER_ALARM_CLOCK, + REASON_ALARM_MANAGER_WHILE_IDLE, + REASON_SERVICE_LAUNCH, + REASON_KEY_CHAIN, + REASON_PACKAGE_VERIFIER, + REASON_SYNC_MANAGER, + REASON_DOMAIN_VERIFICATION_V1, + REASON_DOMAIN_VERIFICATION_V2, + REASON_VPN, + REASON_NOTIFICATION_SERVICE, + REASON_PACKAGE_REPLACED, + REASON_LOCATION_PROVIDER, + REASON_MEDIA_BUTTON, + REASON_EVENT_SMS, + REASON_EVENT_MMS, + REASON_SHELL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ReasonCode {} + /** * @hide */ @@ -184,19 +425,34 @@ public class PowerWhitelistManager { * * @param packageName The package to add to the temp whitelist * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason a optional human readable reason string, could be null or empty string. */ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) - public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) { - String reason = "from:" + UserHandle.formatUid(Binder.getCallingUid()); + public void whitelistAppTemporarily(@NonNull String packageName, long durationMs, + @ReasonCode int reasonCode, @Nullable String reason) { try { mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(), - reason); + reasonCode, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Add an app to the temporary whitelist for a short amount of time. + * + * @param packageName The package to add to the temp whitelist + * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) + * @deprecated Use {@link #whitelistAppTemporarily(String, long, int, String)} instead + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) { + whitelistAppTemporarily(packageName, durationMs, REASON_UNKNOWN, packageName); + } + + /** * Add an app to the temporary whitelist for a short amount of time for a specific reason. The * temporary whitelist is kept separately from the permanent whitelist and apps are * automatically removed from the temporary whitelist after a predetermined amount of time. @@ -204,27 +460,179 @@ public class PowerWhitelistManager { * @param packageName The package to add to the temp whitelist * @param event The reason to add the app to the temp whitelist * @param reason A human-readable reason explaining why the app is temp whitelisted. Only - * used for logging purposes + * used for logging purposes. Could be null or empty string. * @return The duration (in milliseconds) that the app is whitelisted for + * @deprecated Use {@link #whitelistAppTemporarilyForEvent(String, int, int, String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String packageName, - @WhitelistEvent int event, @NonNull String reason) { + @WhitelistEvent int event, @Nullable String reason) { + return whitelistAppTemporarilyForEvent(packageName, event, REASON_UNKNOWN, reason); + } + + /** + * Add an app to the temporary whitelist for a short amount of time for a specific reason. The + * temporary whitelist is kept separately from the permanent whitelist and apps are + * automatically removed from the temporary whitelist after a predetermined amount of time. + * + * @param packageName The package to add to the temp whitelist + * @param event The reason to add the app to the temp whitelist + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason A human-readable reason explaining why the app is temp whitelisted. Only + * used for logging purposes. Could be null or empty string. + * @return The duration (in milliseconds) that the app is whitelisted for + */ + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public long whitelistAppTemporarilyForEvent(@NonNull String packageName, + @WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) { try { switch (event) { case EVENT_MMS: return mService.addPowerSaveTempWhitelistAppForMms( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); case EVENT_SMS: return mService.addPowerSaveTempWhitelistAppForSms( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); case EVENT_UNSPECIFIED: default: return mService.whitelistAppTemporarily( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** + * @hide + */ + public static @ReasonCode int getReasonCodeFromProcState(int procState) { + if (procState <= PROCESS_STATE_PERSISTENT) { + return REASON_PROC_STATE_PERSISTENT; + } else if (procState <= PROCESS_STATE_PERSISTENT_UI) { + return REASON_PROC_STATE_PERSISTENT_UI; + } else if (procState <= PROCESS_STATE_TOP) { + return REASON_PROC_STATE_TOP; + } else if (procState <= PROCESS_STATE_BOUND_TOP) { + return REASON_PROC_STATE_BTOP; + } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_FGS; + } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_BFGS; + } else { + return REASON_DENIED; + } + } + + /** + * Return string name of the integer reason code. + * @hide + * @param reasonCode + * @return string name of the reason code. + */ + public static String reasonCodeToString(@ReasonCode int reasonCode) { + switch (reasonCode) { + case REASON_DENIED: + return "DENIED"; + case REASON_UNKNOWN: + return "UNKNOWN"; + case REASON_OTHER: + return "OTHER"; + case REASON_PROC_STATE_PERSISTENT: + return "PROC_STATE_PERSISTENT"; + case REASON_PROC_STATE_PERSISTENT_UI: + return "PROC_STATE_PERSISTENT_UI"; + case REASON_PROC_STATE_TOP: + return "PROC_STATE_TOP"; + case REASON_PROC_STATE_BTOP: + return "PROC_STATE_BTOP"; + case REASON_PROC_STATE_FGS: + return "PROC_STATE_FGS"; + case REASON_PROC_STATE_BFGS: + return "PROC_STATE_BFGS"; + case REASON_UID_VISIBLE: + return "UID_VISIBLE"; + case REASON_SYSTEM_UID: + return "SYSTEM_UID"; + case REASON_ACTIVITY_STARTER: + return "ACTIVITY_STARTER"; + case REASON_START_ACTIVITY_FLAG: + return "START_ACTIVITY_FLAG"; + case REASON_FGS_BINDING: + return "FGS_BINDING"; + case REASON_DEVICE_OWNER: + return "DEVICE_OWNER"; + case REASON_PROFILE_OWNER: + return "PROFILE_OWNER"; + case REASON_COMPANION_DEVICE_MANAGER: + return "COMPANION_DEVICE_MANAGER"; + case REASON_BACKGROUND_ACTIVITY_PERMISSION: + return "BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_BACKGROUND_FGS_PERMISSION: + return "BACKGROUND_FGS_PERMISSION"; + case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION: + return "INSTR_BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_INSTR_BACKGROUND_FGS_PERMISSION: + return "INSTR_BACKGROUND_FGS_PERMISSION"; + case REASON_SYSTEM_ALERT_WINDOW_PERMISSION: + return "SYSTEM_ALERT_WINDOW_PERMISSION"; + case REASON_DEVICE_DEMO_MODE: + return "DEVICE_DEMO_MODE"; + case REASON_EXEMPTED_PACKAGE: + return "EXEMPTED_PACKAGE"; + case REASON_ALLOWLISTED_PACKAGE: + return "ALLOWLISTED_PACKAGE"; + case REASON_APPOP: + return "APPOP"; + case REASON_GEOFENCING: + return "GEOFENCING"; + case REASON_PUSH_MESSAGING: + return "PUSH_MESSAGING"; + case REASON_ACTIVITY_RECOGNITION: + return "ACTIVITY_RECOGNITION"; + case REASON_BOOT_COMPLETED: + return "BOOT_COMPLETED"; + case REASON_PRE_BOOT_COMPLETED: + return "PRE_BOOT_COMPLETED"; + case REASON_LOCKED_BOOT_COMPLETED: + return "LOCKED_BOOT_COMPLETED"; + case REASON_SYSTEM_ALLOW_LISTED: + return "SYSTEM_ALLOW_LISTED"; + case REASON_ALARM_MANAGER_ALARM_CLOCK: + return "ALARM_MANAGER_ALARM_CLOCK"; + case REASON_ALARM_MANAGER_WHILE_IDLE: + return "ALARM_MANAGER_WHILE_IDLE"; + case REASON_SERVICE_LAUNCH: + return "SERVICE_LAUNCH"; + case REASON_KEY_CHAIN: + return "KEY_CHAIN"; + case REASON_PACKAGE_VERIFIER: + return "PACKAGE_VERIFIER"; + case REASON_SYNC_MANAGER: + return "SYNC_MANAGER"; + case REASON_DOMAIN_VERIFICATION_V1: + return "DOMAIN_VERIFICATION_V1"; + case REASON_DOMAIN_VERIFICATION_V2: + return "DOMAIN_VERIFICATION_V2"; + case REASON_VPN: + return "VPN"; + case REASON_NOTIFICATION_SERVICE: + return "NOTIFICATION_SERVICE"; + case REASON_PACKAGE_REPLACED: + return "PACKAGE_REPLACED"; + case REASON_LOCATION_PROVIDER: + return "LOCATION_PROVIDER"; + case REASON_MEDIA_BUTTON: + return "MEDIA_BUTTON"; + case REASON_EVENT_SMS: + return "EVENT_SMS"; + case REASON_EVENT_MMS: + return "EVENT_MMS"; + case REASON_SHELL: + return "SHELL"; + default: + return "(unknown:" + reasonCode + ")"; + } + } } diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index e045b0fa3a6b..5e5717d11432 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -16,6 +16,8 @@ package com.android.server; +import android.annotation.Nullable; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import com.android.server.deviceidle.IDeviceIdleConstraint; @@ -34,6 +36,10 @@ public interface DeviceIdleInternal { void addPowerSaveTempWhitelistApp(int callingUid, String packageName, long duration, int userId, boolean sync, String reason); + void addPowerSaveTempWhitelistApp(int callingUid, String packageName, + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason); + /** * Called by ActivityManagerService to directly add UID to DeviceIdleController's temp * allowlist. @@ -41,11 +47,12 @@ public interface DeviceIdleInternal { * @param duration duration in milliseconds * @param type temp allowlist type defined at {@link TempAllowListType} * @param sync + * @param reasonCode one of {@link ReasonCode} * @param reason */ void addPowerSaveTempWhitelistAppDirect(int uid, long duration, - @TempAllowListType int type, boolean sync, - String reason); + @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason); // duration in milliseconds long getNotificationAllowlistDuration(); diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 8f7f705163ba..ac28e828eb2e 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -16,12 +16,17 @@ package com.android.server; +import static android.os.PowerWhitelistManager.REASON_SHELL; +import static android.os.PowerWhitelistManager.REASON_UNKNOWN; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.Process.INVALID_UID; + import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; -import android.app.BroadcastOptions; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -56,6 +61,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteException; @@ -1838,31 +1844,34 @@ public class DeviceIdleController extends SystemService } @Override - public long whitelistAppTemporarily(String packageName, int userId, String reason) - throws RemoteException { + public long whitelistAppTemporarily(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { // At least 10 seconds. long durationMs = Math.max(10_000L, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS / 2); - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } @Override - public void addPowerSaveTempWhitelistApp(String packageName, long duration, - int userId, String reason) throws RemoteException { - addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reason); + public void addPowerSaveTempWhitelistApp(String packageName, long duration, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { + addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reasonCode, reason); } - @Override public long addPowerSaveTempWhitelistAppForMms(String packageName, - int userId, String reason) throws RemoteException { + @Override public long addPowerSaveTempWhitelistAppForMms(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { long durationMs = mConstants.MMS_TEMP_APP_ALLOWLIST_DURATION_MS; - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } - @Override public long addPowerSaveTempWhitelistAppForSms(String packageName, - int userId, String reason) throws RemoteException { + @Override public long addPowerSaveTempWhitelistAppForSms(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { long durationMs = mConstants.SMS_TEMP_APP_ALLOWLIST_DURATION_MS; - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } @@ -1934,18 +1943,29 @@ public class DeviceIdleController extends SystemService } // duration in milliseconds + @Deprecated @Override public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, String reason) { + long duration, int userId, boolean sync, @Nullable String reason) { addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, - userId, sync, reason); + userId, sync, REASON_UNKNOWN, reason); + } + + @Override + public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { + addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, + userId, sync, reasonCode, reason); } // duration in milliseconds @Override public void addPowerSaveTempWhitelistAppDirect(int uid, long duration, - @TempAllowListType int type, boolean sync, String reason) { - addPowerSaveTempWhitelistAppDirectInternal(0, uid, duration, type, sync, reason); + @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { + addPowerSaveTempWhitelistAppDirectInternal(0, uid, duration, type, sync, + reasonCode, reason); } // duration in milliseconds @@ -2293,7 +2313,7 @@ public class DeviceIdleController extends SystemService filter.addAction(Intent.ACTION_SCREEN_ON); getContext().registerReceiver(mInteractivityReceiver, filter); - mLocalActivityManager.setDeviceIdleWhitelist( + mLocalActivityManager.setDeviceIdleAllowlist( mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); @@ -2671,7 +2691,8 @@ public class DeviceIdleController extends SystemService } void addPowerSaveTempAllowlistAppChecked(String packageName, long duration, - int userId, String reason) throws RemoteException { + int userId, @ReasonCode int reasonCode, @Nullable String reason) + throws RemoteException { getContext().enforceCallingPermission( Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, "No permission to change device idle whitelist"); @@ -2686,7 +2707,7 @@ public class DeviceIdleController extends SystemService final long token = Binder.clearCallingIdentity(); try { addPowerSaveTempAllowlistAppInternal(callingUid, - packageName, duration, userId, true, reason); + packageName, duration, userId, true, reasonCode, reason); } finally { Binder.restoreCallingIdentity(token); } @@ -2718,12 +2739,12 @@ public class DeviceIdleController extends SystemService * app an exemption to access network and acquire wakelocks. */ void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName, - long duration, int userId, boolean sync, String reason) { + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { try { int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId); addPowerSaveTempWhitelistAppDirectInternal(callingUid, uid, duration, - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, sync, - reason); + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, sync, reasonCode, reason); } catch (NameNotFoundException e) { } } @@ -2733,7 +2754,8 @@ public class DeviceIdleController extends SystemService * app an exemption to access network and acquire wakelocks. */ void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid, - long duration, @TempAllowListType int type, boolean sync, String reason) { + long duration, @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { final long timeNow = SystemClock.elapsedRealtime(); boolean informWhitelistChanged = false; int appId = UserHandle.getAppId(uid); @@ -2765,7 +2787,8 @@ public class DeviceIdleController extends SystemService } catch (RemoteException e) { } postTempActiveTimeoutMessage(uid, duration); - updateTempWhitelistAppIdsLocked(uid, true, duration, type); + updateTempWhitelistAppIdsLocked(uid, true, duration, type, reasonCode, + reason, callingUid); if (sync) { informWhitelistChanged = true; } else { @@ -2844,12 +2867,13 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - private void onAppRemovedFromTempWhitelistLocked(int uid, String reason) { + private void onAppRemovedFromTempWhitelistLocked(int uid, @Nullable String reason) { if (DEBUG) { Slog.d(TAG, "Removing uid " + uid + " from temp whitelist"); } final int appId = UserHandle.getAppId(uid); - updateTempWhitelistAppIdsLocked(uid, false, 0, 0); + updateTempWhitelistAppIdsLocked(uid, false, 0, 0, REASON_UNKNOWN, + reason, INVALID_UID); mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS, appId, 0) .sendToTarget(); reportTempWhitelistChangedLocked(uid, false); @@ -3860,7 +3884,7 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds); if (mLocalActivityManager != null) { - mLocalActivityManager.setDeviceIdleWhitelist( + mLocalActivityManager.setDeviceIdleAllowlist( mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); } if (mLocalPowerManager != null) { @@ -3880,9 +3904,14 @@ public class DeviceIdleController extends SystemService * @param durationMs duration in milliseconds to add to temp allowlist, only valid when * param adding is true. * @param type temp allowlist type defined at {@link TempAllowListType} + * @prama reasonCode one of {@Link ReasonCode} + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp-allowlist, only valid when param adding + * is true. */ private void updateTempWhitelistAppIdsLocked(int uid, boolean adding, long durationMs, - @TempAllowListType int type) { + @TempAllowListType int type, @ReasonCode int reasonCode, @Nullable String reason, + int callingUid) { final int size = mTempWhitelistAppIdEndTimes.size(); if (mTempWhitelistAppIdArray.length != size) { mTempWhitelistAppIdArray = new int[size]; @@ -3895,8 +3924,8 @@ public class DeviceIdleController extends SystemService Slog.d(TAG, "Setting activity manager temp whitelist to " + Arrays.toString(mTempWhitelistAppIdArray)); } - mLocalActivityManager.updateDeviceIdleTempWhitelist(mTempWhitelistAppIdArray, uid, - adding, durationMs, type); + mLocalActivityManager.updateDeviceIdleTempAllowlist(mTempWhitelistAppIdArray, uid, + adding, durationMs, type, reasonCode, reason, callingUid); } if (mLocalPowerManager != null) { if (DEBUG) { @@ -4428,7 +4457,8 @@ public class DeviceIdleController extends SystemService if (removePkg) { removePowerSaveTempAllowlistAppChecked(arg, shell.userId); } else { - addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, "shell"); + addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, + REASON_SHELL, "shell"); } } catch (Exception e) { pw.println("Failed: " + e); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 559a43491c7f..ea733696e1f7 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -423,6 +423,8 @@ public class AlarmManagerService extends SystemService { static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota"; private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window"; + private static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps"; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS; @@ -454,6 +456,8 @@ public class AlarmManagerService extends SystemService { private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72; private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour. + // TODO (b/171306433): Change to true by default. + private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -495,6 +499,13 @@ public class AlarmManagerService extends SystemService { */ public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW; + /** + * Whether or not to crash callers that use setExactAndAllowWhileIdle or setAlarmClock + * but don't hold the required permission. This is useful to catch broken + * apps and reverting to a softer failure in case of broken apps. + */ + public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -607,6 +618,10 @@ public class AlarmManagerService extends SystemService { KEY_TIME_TICK_ALLOWED_WHILE_IDLE, DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE); break; + case KEY_CRASH_NON_CLOCK_APPS: + CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS, + DEFAULT_CRASH_NON_CLOCK_APPS); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -741,6 +756,9 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_TIME_TICK_ALLOWED_WHILE_IDLE, TIME_TICK_ALLOWED_WHILE_IDLE); pw.println(); + pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS); + pw.println(); + pw.decreaseIndent(); } @@ -1914,11 +1932,12 @@ public class AlarmManagerService extends SystemService { } /** - * Returns true if the given uid is on the system or user's power save exclusion list. + * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, + * allow-while-idle alarms. */ - boolean isWhitelisted(int uid) { - return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist( - UserHandle.getAppId(uid))); + boolean isExemptFromPermission(int uid) { + return (UserHandle.isSameApp(mSystemUiUid, uid) || mLocalDeviceIdleController == null + || mLocalDeviceIdleController.isAppOnWhitelist(UserHandle.getAppId(uid))); } /** @@ -1949,7 +1968,7 @@ public class AlarmManagerService extends SystemService { if (windowLength != AlarmManager.WINDOW_EXACT) { needsPermission = false; lowQuota = true; - idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle() + idleOptions = isExemptFromPermission(callingUid) ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle(); } else if (alarmClock != null) { needsPermission = true; @@ -1966,16 +1985,22 @@ public class AlarmManagerService extends SystemService { idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; } if (needsPermission && !canScheduleExactAlarms()) { - if (alarmClock == null && isWhitelisted(callingUid)) { + if (alarmClock == null && isExemptFromPermission(callingUid)) { // If the app is on the full system allow-list (not except-idle), we still // allow the alarms, but with a lower quota to keep pre-S compatibility. lowQuota = true; } else { - final String errorMessage = "Caller needs to hold " + final String errorMessage = "Caller " + callingPackage + " needs to hold " + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock") + " alarms."; - throw new SecurityException(errorMessage); + if (mConstants.CRASH_NON_CLOCK_APPS) { + throw new SecurityException(errorMessage); + } else { + Slog.wtf(TAG, errorMessage); + idleOptions = mOptsWithoutFgs.toBundle(); + lowQuota = allowWhileIdle; + } } } if (lowQuota) { @@ -2933,7 +2958,7 @@ public class AlarmManagerService extends SystemService { if (UserHandle.isCore(uid) || uid == mSystemUiUid) { return; } - if (isWhitelisted(uid)) { + if (isExemptFromPermission(uid)) { return; } if (!CompatChanges.isChangeEnabled( diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index c5d3b7a726b9..040a1164fc73 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -53,7 +53,6 @@ import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.LimitExceededException; import android.os.Looper; @@ -153,8 +152,7 @@ public class JobSchedulerService extends com.android.server.SystemService /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; /** The number of the most recently completed jobs to keep track of for debugging purposes. */ - private static final int NUM_COMPLETED_JOB_HISTORY = - Build.IS_USERDEBUG || Build.IS_ENG ? 25 : 0; + private static final int NUM_COMPLETED_JOB_HISTORY = 20; @VisibleForTesting public static Clock sSystemClock = Clock.systemUTC(); @@ -889,8 +887,10 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserStarting(@NonNull TargetUser user) { + public void onUserUnlocked(@NonNull TargetUser user) { synchronized (mLock) { + // Note that the user has started after its unlocked instead of when the user + // actually starts because the storage won't be decrypted until unlock. mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier()); } // Let's kick any outstanding jobs for this user. @@ -898,12 +898,6 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserUnlocking(@NonNull TargetUser user) { - // Let's kick any outstanding jobs for this user. - mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); - } - - @Override public void onUserStopping(@NonNull TargetUser user) { synchronized (mLock) { mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index be91947b0445..2a23d60d8af6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -271,7 +271,9 @@ public final class JobServiceContext implements ServiceConnection { if (job.shouldTreatAsExpeditedJob()) { // TODO(171305774): The job should run on the little cores. We'll probably need // another binding flag for that. - bindFlags = Context.BIND_AUTO_CREATE; + bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALMOST_PERCEPTIBLE + | Context.BIND_ALLOW_NETWORK_ACCESS; } else { bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_PERCEPTIBLE; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 824fa7fc1659..2faa8360bf44 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -828,8 +828,8 @@ public final class QuotaController extends StateController { } @NonNull - private ShrinkableDebits getEJQuotaLocked(final int userId, - @NonNull final String packageName) { + @VisibleForTesting + ShrinkableDebits getEJDebitsLocked(final int userId, @NonNull final String packageName) { ShrinkableDebits debits = mEJStats.get(userId, packageName); if (debits == null) { debits = new ShrinkableDebits( @@ -931,7 +931,7 @@ public final class QuotaController extends StateController { @VisibleForTesting long getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName) { - ShrinkableDebits quota = getEJQuotaLocked(userId, packageName); + ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); if (quota.getStandbyBucketLocked() == NEVER_INDEX) { return 0; } @@ -948,7 +948,7 @@ public final class QuotaController extends StateController { if (ts.endTimeElapsed < windowStartTimeElapsed) { final long duration = ts.endTimeElapsed - ts.startTimeElapsed; remainingMs += duration; - quota.transactOnDebitsLocked(-duration); + quota.transactLocked(-duration); timingSessions.remove(0); } else if (ts.startTimeElapsed < windowStartTimeElapsed) { remainingMs += windowStartTimeElapsed - ts.startTimeElapsed; @@ -960,15 +960,16 @@ public final class QuotaController extends StateController { } } + TopAppTimer topAppTimer = mTopAppTrackers.get(userId, packageName); + if (topAppTimer != null && topAppTimer.isActive()) { + remainingMs += topAppTimer.getPendingReward(nowElapsed); + } + Timer timer = mEJPkgTimers.get(userId, packageName); if (timer == null) { return remainingMs; } - // There's a case where the debits tally is 0 but a currently running HPJ still counts - // towards quota. If the app gets a reward in this case, the reward is lost and the HPJ - // run is still fully counted. - // TODO(171305774)/STOPSHIP: make sure getting rewards while HPJ currently executing isn't - // treated negatively + return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis()); } @@ -1081,7 +1082,7 @@ public final class QuotaController extends StateController { } final long nowElapsed = sElapsedRealtimeClock.millis(); - ShrinkableDebits quota = getEJQuotaLocked(userId, packageName); + ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked()); final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs); long remainingDeadSpaceMs = remainingExecutionTimeMs; @@ -1372,6 +1373,11 @@ public final class QuotaController extends StateController { @VisibleForTesting void saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session, boolean isExpedited) { + saveTimingSession(userId, packageName, session, isExpedited, 0); + } + + private void saveTimingSession(final int userId, @NonNull final String packageName, + @NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) { synchronized (mLock) { final SparseArrayMap<String, List<TimingSession>> sessionMap = isExpedited ? mEJTimingSessions : mTimingSessions; @@ -1382,8 +1388,9 @@ public final class QuotaController extends StateController { } sessions.add(session); if (isExpedited) { - final ShrinkableDebits quota = getEJQuotaLocked(userId, packageName); - quota.transactOnDebitsLocked(session.endTimeElapsed - session.startTimeElapsed); + final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); + quota.transactLocked(session.endTimeElapsed - session.startTimeElapsed + + debitAdjustment); } else { // Adding a new session means that the current stats are now incorrect. invalidateAllExecutionStatsLocked(userId, packageName); @@ -1396,15 +1403,34 @@ public final class QuotaController extends StateController { private void grantRewardForInstantEvent( final int userId, @NonNull final String packageName, final long credit) { synchronized (mLock) { - final ShrinkableDebits quota = getEJQuotaLocked(userId, packageName); - quota.transactOnDebitsLocked(-credit); - if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), - userId, packageName)) { + final long nowElapsed = sElapsedRealtimeClock.millis(); + final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); + if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit) + && maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) { mStateChangedListener.onControllerStateChanged(); } } } + private boolean transactQuotaLocked(final int userId, @NonNull final String packageName, + final long nowElapsed, @NonNull ShrinkableDebits debits, final long credit) { + final long oldTally = debits.getTallyLocked(); + final long leftover = debits.transactLocked(-credit); + if (DEBUG) { + Slog.d(TAG, "debits overflowed by " + leftover); + } + boolean changed = oldTally != debits.getTallyLocked(); + if (leftover != 0) { + // Only adjust timer if its active. + final Timer ejTimer = mEJPkgTimers.get(userId, packageName); + if (ejTimer != null && ejTimer.isActive()) { + ejTimer.updateDebitAdjustment(nowElapsed, leftover); + changed = true; + } + } + return changed; + } + private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> { public long earliestEndElapsed = Long.MAX_VALUE; @@ -1875,7 +1901,8 @@ public final class QuotaController extends StateController { } } - private static final class ShrinkableDebits { + @VisibleForTesting + static final class ShrinkableDebits { /** The amount of quota remaining. Can be negative if limit changes. */ private long mDebitTally; private int mStandbyBucket; @@ -1893,8 +1920,11 @@ public final class QuotaController extends StateController { * Negative if the tally should decrease (therefore increasing available quota); * or positive if the tally should increase (therefore decreasing available quota). */ - void transactOnDebitsLocked(final long amount) { + long transactLocked(final long amount) { + final long leftover = amount < 0 && Math.abs(amount) > mDebitTally + ? mDebitTally + amount : 0; mDebitTally = Math.max(0, mDebitTally + amount); + return leftover; } void setStandbyBucketLocked(int standbyBucket) { @@ -1927,6 +1957,7 @@ public final class QuotaController extends StateController { private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); private long mStartTimeElapsed; private int mBgJobCount; + private long mDebitAdjustment; Timer(int uid, int userId, String packageName, boolean regularJobTimer) { mPkg = new Package(userId, packageName); @@ -1957,6 +1988,7 @@ public final class QuotaController extends StateController { if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); + mDebitAdjustment = 0; if (mRegularJobTimer) { // Starting the timer means that all cached execution stats are now // incorrect. @@ -1988,6 +2020,11 @@ public final class QuotaController extends StateController { } } + void updateDebitAdjustment(long nowElapsed, long debit) { + // Make sure we don't have a credit larger than the expected session. + mDebitAdjustment = Math.max(mDebitAdjustment + debit, mStartTimeElapsed - nowElapsed); + } + /** * Stops tracking all jobs and cancels any pending alarms. This should only be called if * the Timer is not going to be used anymore. @@ -2003,7 +2040,8 @@ public final class QuotaController extends StateController { return; } TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); - saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer); + saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer, + mDebitAdjustment); mBgJobCount = 0; // Don't reset the tracked jobs list as we need to keep tracking the current number // of jobs. @@ -2030,7 +2068,7 @@ public final class QuotaController extends StateController { long getCurrentDuration(long nowElapsed) { synchronized (mLock) { - return !isActive() ? 0 : nowElapsed - mStartTimeElapsed; + return !isActive() ? 0 : nowElapsed - mStartTimeElapsed + mDebitAdjustment; } } @@ -2059,6 +2097,7 @@ public final class QuotaController extends StateController { // Start timing from unplug. if (mRunningBgJobs.size() > 0) { mStartTimeElapsed = nowElapsed; + mDebitAdjustment = 0; // NOTE: this does have the unfortunate consequence that if the device is // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. @@ -2128,6 +2167,11 @@ public final class QuotaController extends StateController { pw.print(", "); pw.print(mBgJobCount); pw.print(" running bg jobs"); + if (!mRegularJobTimer) { + pw.print(" (debit adj="); + pw.print(mDebitAdjustment); + pw.print(")"); + } pw.println(); pw.increaseIndent(); for (int i = 0; i < mRunningBgJobs.size(); i++) { @@ -2171,6 +2215,21 @@ public final class QuotaController extends StateController { mPkg = new Package(userId, packageName); } + private int calculateTimeChunks(final long nowElapsed) { + final long totalTopTimeMs = nowElapsed - mStartTimeElapsed; + int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs); + final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs; + if (remainderMs >= SECOND_IN_MILLIS) { + // "Round up" + numTimeChunks++; + } + return numTimeChunks; + } + + long getPendingReward(final long nowElapsed) { + return mEJRewardTopAppMs * calculateTimeChunks(nowElapsed); + } + void processEventLocked(@NonNull UsageEvents.Event event) { final long nowElapsed = sElapsedRealtimeClock.millis(); switch (event.getEventType()) { @@ -2186,21 +2245,16 @@ public final class QuotaController extends StateController { final UsageEvents.Event existingEvent = mActivities.removeReturnOld(event.mInstanceId); if (existingEvent != null && mActivities.size() == 0) { - final long totalTopTimeMs = nowElapsed - mStartTimeElapsed; - int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs); - final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs; - if (remainderMs >= SECOND_IN_MILLIS) { - // "Round up" - numTimeChunks++; - } + final long pendingReward = getPendingReward(nowElapsed); if (DEBUG) { - Slog.d(TAG, - "Crediting " + mPkg + " for " + numTimeChunks + " time chunks"); + Slog.d(TAG, "Crediting " + mPkg + " " + pendingReward + "ms" + + " for " + calculateTimeChunks(nowElapsed) + " time chunks"); } - final ShrinkableDebits quota = - getEJQuotaLocked(mPkg.userId, mPkg.packageName); - quota.transactOnDebitsLocked(-mEJRewardTopAppMs * numTimeChunks); - if (maybeUpdateConstraintForPkgLocked(nowElapsed, + final ShrinkableDebits debits = + getEJDebitsLocked(mPkg.userId, mPkg.packageName); + if (transactQuotaLocked(mPkg.userId, mPkg.packageName, + nowElapsed, debits, pendingReward) + && maybeUpdateConstraintForPkgLocked(nowElapsed, mPkg.userId, mPkg.packageName)) { mStateChangedListener.onControllerStateChanged(); } @@ -2321,7 +2375,7 @@ public final class QuotaController extends StateController { */ @Override public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { - mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event); + mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget(); } } @@ -2408,7 +2462,6 @@ public final class QuotaController extends StateController { } private class QcHandler extends Handler { - private boolean mIsProcessing; QcHandler(Looper looper) { super(looper); @@ -2417,8 +2470,6 @@ public final class QuotaController extends StateController { @Override public void handleMessage(Message msg) { synchronized (mLock) { - mIsProcessing = true; - switch (msg.what) { case MSG_REACHED_QUOTA: { Package pkg = (Package) msg.obj; @@ -2539,6 +2590,10 @@ public final class QuotaController extends StateController { final int userId = msg.arg1; final UsageEvents.Event event = (UsageEvents.Event) msg.obj; final String pkgName = event.getPackageName(); + if (DEBUG) { + Slog.d(TAG, "Processing event " + event.getEventType() + + " for " + string(userId, pkgName)); + } switch (event.getEventType()) { case UsageEvents.Event.ACTIVITY_RESUMED: case UsageEvents.Event.ACTIVITY_PAUSED: @@ -2604,8 +2659,6 @@ public final class QuotaController extends StateController { } } } - - mIsProcessing = false; } } @@ -3883,11 +3936,6 @@ public final class QuotaController extends StateController { return mQcConstants; } - @VisibleForTesting - boolean isActiveBackgroundProcessing() { - return mHandler.mIsProcessing; - } - //////////////////////////// DATA DUMP ////////////////////////////// @Override diff --git a/apex/jobscheduler/service/jni/Android.bp b/apex/jobscheduler/service/jni/Android.bp index 4bcc165e9eea..c630217387ce 100644 --- a/apex/jobscheduler/service/jni/Android.bp +++ b/apex/jobscheduler/service/jni/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + cc_library_shared { name: "libalarm_jni", diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp index 5b24cfa4219b..9b3399e8b0e1 100644 --- a/apex/media/service/Android.bp +++ b/apex/media/service/Android.bp @@ -11,6 +11,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "service-media-s-sources", srcs: [ @@ -38,4 +47,3 @@ java_sdk_library { "com.android.media", ], } - diff --git a/api/Android.bp b/api/Android.bp index 15c1dfcfe153..1fdf1771bb13 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -40,6 +40,7 @@ genrule { ":art.module.public.api{.public.api.txt}", ":conscrypt.module.public.api{.public.api.txt}", ":framework-appsearch{.public.api.txt}", + ":framework-connectivity{.public.api.txt}", ":framework-graphics{.public.api.txt}", ":framework-media{.public.api.txt}", ":framework-mediaprovider{.public.api.txt}", @@ -95,6 +96,7 @@ genrule { ":art.module.public.api{.public.stubs.source}", ":conscrypt.module.public.api{.public.stubs.source}", ":framework-appsearch{.public.stubs.source}", + ":framework-connectivity{.public.stubs.source}", ":framework-graphics{.public.stubs.source}", ":framework-media{.public.stubs.source}", ":framework-mediaprovider{.public.stubs.source}", @@ -120,6 +122,7 @@ genrule { ":art.module.public.api{.public.removed-api.txt}", ":conscrypt.module.public.api{.public.removed-api.txt}", ":framework-appsearch{.public.removed-api.txt}", + ":framework-connectivity{.public.removed-api.txt}", ":framework-graphics{.public.removed-api.txt}", ":framework-media{.public.removed-api.txt}", ":framework-mediaprovider{.public.removed-api.txt}", @@ -155,6 +158,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.system.api.txt}", ":framework-appsearch{.system.api.txt}", + ":framework-connectivity{.system.api.txt}", ":framework-graphics{.system.api.txt}", ":framework-media{.system.api.txt}", ":framework-mediaprovider{.system.api.txt}", @@ -208,6 +212,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.system.removed-api.txt}", ":framework-appsearch{.system.removed-api.txt}", + ":framework-connectivity{.system.removed-api.txt}", ":framework-graphics{.system.removed-api.txt}", ":framework-media{.system.removed-api.txt}", ":framework-mediaprovider{.system.removed-api.txt}", @@ -243,6 +248,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.module-lib.api.txt}", ":framework-appsearch{.module-lib.api.txt}", + ":framework-connectivity{.module-lib.api.txt}", ":framework-graphics{.module-lib.api.txt}", ":framework-media{.module-lib.api.txt}", ":framework-mediaprovider{.module-lib.api.txt}", @@ -298,6 +304,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.module-lib.removed-api.txt}", ":framework-appsearch{.module-lib.removed-api.txt}", + ":framework-connectivity{.module-lib.removed-api.txt}", ":framework-graphics{.module-lib.removed-api.txt}", ":framework-media{.module-lib.removed-api.txt}", ":framework-mediaprovider{.module-lib.removed-api.txt}", @@ -340,3 +347,49 @@ genrule { out: ["combined-removed-dex.txt"], cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)", } + +genrule { + name: "services-system-server-current.txt", + srcs: [ + ":service-permission{.system-server.api.txt}", + ":non-updatable-system-server-current.txt", + ], + out: ["system-server-current.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-current.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-android.txt", + }, + ], +} + +genrule { + name: "services-system-server-removed.txt", + srcs: [ + ":service-permission{.system-server.removed-api.txt}", + ":non-updatable-system-server-removed.txt", + ], + out: ["system-server-removed.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-removed.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-removed.txt", + }, + ], +} diff --git a/cmds/abx/Android.bp b/cmds/abx/Android.bp index 333acedfadad..50a0b75b3276 100644 --- a/cmds/abx/Android.bp +++ b/cmds/abx/Android.bp @@ -1,4 +1,21 @@ +package { + default_applicable_licenses: ["frameworks_base_cmds_abx_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "frameworks_base_cmds_abx_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + java_binary { name: "abx", wrapper: "abx", diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 4b7eda096e54..ed717c491467 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -19,7 +19,6 @@ package com.android.commands.bmgr; import android.annotation.IntDef; import android.annotation.UserIdInt; import android.app.backup.BackupManager; -import android.app.backup.BackupManager.OperationType; import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupProgress; import android.app.backup.BackupTransport; @@ -667,7 +666,7 @@ public class Bmgr { // The rest of the 'list' options work with a restore session on the current transport try { - mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; @@ -822,7 +821,7 @@ public class Bmgr { try { boolean didRestore = false; - mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; diff --git a/cmds/hid/OWNERS b/cmds/hid/OWNERS new file mode 100644 index 000000000000..d701f23cb9b8 --- /dev/null +++ b/cmds/hid/OWNERS @@ -0,0 +1 @@ +include /core/java/android/hardware/input/OWNERS diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index 422c2be44251..2cda57dd67e9 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -48,6 +48,7 @@ static const char* UHID_PATH = "/dev/uhid"; static struct { jmethodID onDeviceOpen; jmethodID onDeviceGetReport; + jmethodID onDeviceSetReport; jmethodID onDeviceOutput; jmethodID onDeviceError; } gDeviceCallbackClassInfo; @@ -113,10 +114,18 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) { checkAndClearException(env, "onDeviceGetReport"); } -void DeviceCallback::onDeviceOutput(uint8_t eventId, uint8_t rType, +void DeviceCallback::onDeviceSetReport(uint8_t rType, const std::vector<uint8_t>& data) { JNIEnv* env = getJNIEnv(); - env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, eventId, rType, + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceSetReport, rType, + toJbyteArray(env, data).get()); + checkAndClearException(env, "onDeviceSetReport"); +} + +void DeviceCallback::onDeviceOutput(uint8_t rType, + const std::vector<uint8_t>& data) { + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, rType, toJbyteArray(env, data).get()); checkAndClearException(env, "onDeviceOutput"); } @@ -262,7 +271,7 @@ int Device::handleEvents(int events) { ALOGD("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id, set_report.rnum, toString(data).c_str()); } - mDeviceCallback->onDeviceOutput(UHID_SET_REPORT, set_report.rtype, data); + mDeviceCallback->onDeviceSetReport(set_report.rtype, data); break; } case UHID_OUTPUT: { @@ -271,7 +280,7 @@ int Device::handleEvents(int events) { if (DEBUG_OUTPUT) { ALOGD("UHID_OUTPUT rtype=%" PRIu8 " data=%s", output.rtype, toString(data).c_str()); } - mDeviceCallback->onDeviceOutput(UHID_OUTPUT, output.rtype, data); + mDeviceCallback->onDeviceOutput(output.rtype, data); break; } default: { @@ -366,8 +375,10 @@ int register_com_android_commands_hid_Device(JNIEnv* env) { env->GetMethodID(clazz, "onDeviceOpen", "()V"); uhid::gDeviceCallbackClassInfo.onDeviceGetReport = env->GetMethodID(clazz, "onDeviceGetReport", "(II)V"); + uhid::gDeviceCallbackClassInfo.onDeviceSetReport = + env->GetMethodID(clazz, "onDeviceSetReport", "(B[B)V"); uhid::gDeviceCallbackClassInfo.onDeviceOutput = - env->GetMethodID(clazz, "onDeviceOutput", "(BB[B)V"); + env->GetMethodID(clazz, "onDeviceOutput", "(B[B)V"); uhid::gDeviceCallbackClassInfo.onDeviceError = env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index bb73132cc20d..d10a9aa3680c 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -31,7 +31,8 @@ public: void onDeviceOpen(); void onDeviceGetReport(uint32_t requestId, uint8_t reportId); - void onDeviceOutput(uint8_t eventId, uint8_t rType, const std::vector<uint8_t>& data); + void onDeviceSetReport(uint8_t rType, const std::vector<uint8_t>& data); + void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data); void onDeviceError(); private: diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 37d0b1c6bfa1..95b1e9a7b57f 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -199,8 +199,8 @@ public class Device { mHandler.sendMessageAtTime(msg, mTimeToSend); } - // native callback - public void onDeviceOutput(byte eventId, byte rtype, byte[] data) { + // Send out the report to HID command output + private void sendReportOutput(byte eventId, byte rtype, byte[] data) { JSONObject json = new JSONObject(); try { json.put("eventId", eventId); @@ -221,6 +221,18 @@ public class Device { throw new RuntimeException(e); } + } + + // native callback + public void onDeviceSetReport(byte rtype, byte[] data) { + // We don't need to reply for the SET_REPORT but just send it to HID output for test + // verification. + sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rtype, data); + } + + // native callback + public void onDeviceOutput(byte rtype, byte[] data) { + sendReportOutput(UHID_EVENT_TYPE_UHID_OUTPUT, rtype, data); if (mOutputs == null) { Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found"); return; diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java index a0361d0a39d3..ca9df39f83bf 100644 --- a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java +++ b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java @@ -182,6 +182,8 @@ public class RequestSync { mExtras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); } else if (opt.equals("--rc") || opt.equals("--require-charging")) { mExtras.putBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, true); + } else if (opt.equals("--ej") || opt.equals("--schedule-as-ej")) { + mExtras.putBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, true); } else if (opt.equals("-e") || opt.equals("--es") || opt.equals("--extra-string")) { final String key = nextArgRequired(); final String value = nextArgRequired(); diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp index 0d7fed2a15c7..260cfc781ebc 100644 --- a/cmds/uinput/Android.bp +++ b/cmds/uinput/Android.bp @@ -1,6 +1,23 @@ // Copyright 2020 The Android Open Source Project // +package { + default_applicable_licenses: ["frameworks_base_cmds_uinput_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "frameworks_base_cmds_uinput_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + java_binary { name: "uinput", wrapper: "uinput", @@ -15,4 +32,4 @@ filegroup { srcs: [ "src/com/android/commands/uinput/InputAbsInfo.aidl", ], -}
\ No newline at end of file +} diff --git a/cmds/uinput/jni/Android.bp b/cmds/uinput/jni/Android.bp index 199bbbd35274..c56adc35b580 100644 --- a/cmds/uinput/jni/Android.bp +++ b/cmds/uinput/jni/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_cmds_uinput_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_cmds_uinput_license"], +} + cc_library_shared { name: "libuinputcommand_jni", diff --git a/core/api/current.txt b/core/api/current.txt index 5b639ff0b00f..3951626cda4d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21,7 +21,6 @@ package android { field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"; field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"; - field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; @@ -134,7 +133,6 @@ package android { field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS"; field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"; field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; - field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; @@ -507,6 +505,7 @@ package android { field public static final int dashGap = 16843175; // 0x10101a7 field public static final int dashWidth = 16843174; // 0x10101a6 field public static final int data = 16842798; // 0x101002e + field public static final int dataExtractionRules = 16844350; // 0x101063e field public static final int datePickerDialogTheme = 16843948; // 0x10104ac field public static final int datePickerMode = 16843955; // 0x10104b3 field public static final int datePickerStyle = 16843612; // 0x101035c @@ -528,6 +527,8 @@ package android { field public static final int detailSocialSummary = 16843428; // 0x10102a4 field public static final int detailsElementBackground = 16843598; // 0x101034e field public static final int dial = 16843010; // 0x1010102 + field public static final int dialTint = 16844342; // 0x1010636 + field public static final int dialTintMode = 16844343; // 0x1010637 field public static final int dialogCornerRadius = 16844145; // 0x1010571 field public static final int dialogIcon = 16843252; // 0x10101f4 field public static final int dialogLayout = 16843255; // 0x10101f7 @@ -725,8 +726,14 @@ package android { field public static final int groupIndicator = 16843019; // 0x101010b field public static final int gwpAsanMode = 16844310; // 0x1010616 field public static final int hand_hour = 16843011; // 0x1010103 + field public static final int hand_hourTint = 16844344; // 0x1010638 + field public static final int hand_hourTintMode = 16844345; // 0x1010639 field public static final int hand_minute = 16843012; // 0x1010104 + field public static final int hand_minuteTint = 16844346; // 0x101063a + field public static final int hand_minuteTintMode = 16844347; // 0x101063b field public static final int hand_second = 16844323; // 0x1010623 + field public static final int hand_secondTint = 16844348; // 0x101063c + field public static final int hand_secondTintMode = 16844349; // 0x101063d field public static final int handle = 16843354; // 0x101025a field public static final int handleProfiling = 16842786; // 0x1010022 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e @@ -5602,6 +5609,7 @@ package android.app { field public static final int DEFAULT_LIGHTS = 4; // 0x4 field public static final int DEFAULT_SOUND = 1; // 0x1 field public static final int DEFAULT_VIBRATE = 2; // 0x2 + field public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; @@ -5613,6 +5621,7 @@ package android.app { field public static final String EXTRA_COLORIZED = "android.colorized"; field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; + field public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; @@ -5900,6 +5909,8 @@ package android.app { method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent); method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent); method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent); + method @NonNull public android.app.Notification.CallStyle setAnswerButtonColorHint(@ColorInt int); + method @NonNull public android.app.Notification.CallStyle setDeclineButtonColorHint(@ColorInt int); method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon); method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence); } @@ -6257,14 +6268,14 @@ package android.app { method public static android.app.PendingIntent getActivities(android.content.Context, int, @NonNull android.content.Intent[], int, @Nullable android.os.Bundle); method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int); method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle); - method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int); - method @Nullable public String getCreatorPackage(); + method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int); + method @NonNull public String getCreatorPackage(); method public int getCreatorUid(); - method @Nullable public android.os.UserHandle getCreatorUserHandle(); + method @NonNull public android.os.UserHandle getCreatorUserHandle(); method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int); - method public android.content.IntentSender getIntentSender(); + method @NonNull public android.content.IntentSender getIntentSender(); method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int); - method @Deprecated public String getTargetPackage(); + method @Deprecated @NonNull public String getTargetPackage(); method public boolean isActivity(); method public boolean isBroadcast(); method public boolean isForegroundService(); @@ -8363,7 +8374,9 @@ package android.appwidget { method protected android.view.View getDefaultView(); method protected android.view.View getErrorView(); method protected void prepareView(android.view.View); + method public void resetColorResources(); method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo); + method public void setColorResources(@NonNull android.util.SparseIntArray); method public void setCurrentSize(@NonNull android.graphics.PointF); method public void setExecutor(java.util.concurrent.Executor); method public void setOnLightBackground(boolean); @@ -8444,7 +8457,7 @@ package android.appwidget { method public int describeContents(); method public final android.os.UserHandle getProfile(); method @NonNull public android.content.pm.ActivityInfo getProviderInfo(); - method @Nullable public final String loadDescription(@NonNull android.content.Context); + method @Nullable public final CharSequence loadDescription(@NonNull android.content.Context); method public final android.graphics.drawable.Drawable loadIcon(@NonNull android.content.Context, int); method public final String loadLabel(android.content.pm.PackageManager); method public final android.graphics.drawable.Drawable loadPreviewImage(@NonNull android.content.Context, int); @@ -8462,7 +8475,7 @@ package android.appwidget { field public static final int WIDGET_FEATURE_RECONFIGURABLE = 1; // 0x1 field public int autoAdvanceViewId; field public android.content.ComponentName configure; - field @IdRes public int descriptionResource; + field @IdRes public int descriptionRes; field public int icon; field public int initialKeyguardLayout; field public int initialLayout; @@ -9726,7 +9739,7 @@ package android.companion { public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { } - public class DeviceNotAssociatedException extends java.lang.Exception { + public class DeviceNotAssociatedException extends java.lang.RuntimeException { } public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> { @@ -10230,6 +10243,7 @@ package android.content { field public static final String SYNC_EXTRAS_MANUAL = "force"; field public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override"; field public static final String SYNC_EXTRAS_REQUIRE_CHARGING = "require_charging"; + field public static final String SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB = "schedule_as_expedited_job"; field public static final String SYNC_EXTRAS_UPLOAD = "upload"; field public static final int SYNC_OBSERVER_TYPE_ACTIVE = 4; // 0x4 field public static final int SYNC_OBSERVER_TYPE_PENDING = 2; // 0x2 @@ -10373,7 +10387,7 @@ package android.content { method public abstract void grantUriPermission(String, android.net.Uri, int); method public abstract boolean isDeviceProtectedStorage(); method public boolean isRestricted(); - method public static boolean isUiContext(@NonNull android.content.Context); + method public boolean isUiContext(); method public abstract boolean moveDatabaseFrom(android.content.Context, String); method public abstract boolean moveSharedPreferencesFrom(android.content.Context, String); method @NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[]); @@ -11552,6 +11566,7 @@ package android.content { method public android.content.SyncRequest.Builder setManual(boolean); method public android.content.SyncRequest.Builder setNoRetry(boolean); method public android.content.SyncRequest.Builder setRequiresCharging(boolean); + method @NonNull public android.content.SyncRequest.Builder setScheduleAsExpeditedJob(boolean); method public android.content.SyncRequest.Builder setSyncAdapter(android.accounts.Account, String); method public android.content.SyncRequest.Builder syncOnce(); method public android.content.SyncRequest.Builder syncPeriodic(long, long); @@ -12546,7 +12561,6 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; - field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8 field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -12685,7 +12699,6 @@ package android.content.pm { field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4 field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10 field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000 - field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20 field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8 field public static final int PROTECTION_DANGEROUS = 1; // 0x1 field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40 @@ -18551,23 +18564,6 @@ package android.hardware.camera2.params { package android.hardware.display { - public final class DeviceProductInfo implements android.os.Parcelable { - method public int describeContents(); - method public int getConnectionToSinkType(); - method public int getManufactureWeek(); - method public int getManufactureYear(); - method @NonNull public String getManufacturerPnpId(); - method public int getModelYear(); - method @Nullable public String getName(); - method @NonNull public String getProductId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CONNECTION_TO_SINK_BUILT_IN = 1; // 0x1 - field public static final int CONNECTION_TO_SINK_DIRECT = 2; // 0x2 - field public static final int CONNECTION_TO_SINK_TRANSITIVE = 3; // 0x3 - field public static final int CONNECTION_TO_SINK_UNKNOWN = 0; // 0x0 - field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.DeviceProductInfo> CREATOR; - } - public final class DisplayManager { method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int); method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); @@ -18896,6 +18892,7 @@ package android.inputmethodservice { public abstract class AbstractInputMethodService extends android.app.Service implements android.view.KeyEvent.Callback { ctor public AbstractInputMethodService(); method public android.view.KeyEvent.DispatcherState getKeyDispatcherState(); + method public final boolean isUiContext(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); @@ -19661,6 +19658,7 @@ package android.location { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent); method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties); + method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties, @NonNull java.util.Set<java.lang.String>); method @Deprecated public void clearTestProviderEnabled(@NonNull String); method @Deprecated public void clearTestProviderLocation(@NonNull String); method @Deprecated public void clearTestProviderStatus(@NonNull String); @@ -22149,6 +22147,14 @@ package android.media { field public static final String KEY_TILE_HEIGHT = "tile-height"; field public static final String KEY_TILE_WIDTH = "tile-width"; field public static final String KEY_TRACK_ID = "track-id"; + field public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max"; + field public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; + field public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max"; + field public static final String KEY_VIDEO_QP_I_MIN = "video-qp-i-min"; + field public static final String KEY_VIDEO_QP_MAX = "video-qp-max"; + field public static final String KEY_VIDEO_QP_MIN = "video-qp-min"; + field public static final String KEY_VIDEO_QP_P_MAX = "video-qp-p-max"; + field public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min"; field public static final String KEY_WIDTH = "width"; field public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; field public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3"; @@ -25817,161 +25823,6 @@ package android.mtp { package android.net { - public class CaptivePortal implements android.os.Parcelable { - method public int describeContents(); - method public void ignoreNetwork(); - method public void reportCaptivePortalDismissed(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR; - } - - public class ConnectivityDiagnosticsManager { - method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - } - - public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); - method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport); - method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport); - method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); - } - - public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method @NonNull public android.os.PersistableBundle getAdditionalInfo(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; - field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; - field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; - field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; - field public static final int NETWORK_PROBE_DNS = 4; // 0x4 - field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 - field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 - field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 - field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 - field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 - field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 - field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 - field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 - } - - public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method public int getDetectionMethod(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method @NonNull public android.os.PersistableBundle getStallDetails(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; - field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 - field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 - field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; - field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; - field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; - } - - public class ConnectivityManager { - method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); - method public boolean bindProcessToNetwork(@Nullable android.net.Network); - method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); - method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); - method @Deprecated public boolean getBackgroundDataSetting(); - method @Nullable public android.net.Network getBoundNetworkForProcess(); - method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); - method @Nullable public android.net.ProxyInfo getDefaultProxy(); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); - method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); - method @Nullable public byte[] getNetworkWatchlistConfigHash(); - method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); - method public int getRestrictBackgroundStatus(); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); - method public boolean isDefaultNetworkActive(); - method @Deprecated public static boolean isNetworkTypeValid(int); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); - method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); - method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); - method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); - method public boolean requestBandwidthUpdate(@NonNull android.net.Network); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method @Deprecated public void setNetworkPreference(int); - method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); - method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); - field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; - field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; - field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; - field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; - field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 - field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; - field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; - field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; - field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; - field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; - field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; - field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; - field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; - field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; - field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; - field public static final String EXTRA_REASON = "reason"; - field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 - field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 - field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 - field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 - field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 - field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 - field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 - field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 - field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 - field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 - field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 - field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 - field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 - field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 - field @Deprecated public static final int TYPE_VPN = 17; // 0x11 - field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 - field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 - } - - public static class ConnectivityManager.NetworkCallback { - ctor public ConnectivityManager.NetworkCallback(); - method public void onAvailable(@NonNull android.net.Network); - method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); - method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); - method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); - method public void onLosing(@NonNull android.net.Network, int); - method public void onLost(@NonNull android.net.Network); - method public void onUnavailable(); - } - - public static interface ConnectivityManager.OnNetworkActiveListener { - method public void onNetworkActive(); - } - public class Credentials { ctor public Credentials(int, int, int); method public int getGid(); @@ -25979,46 +25830,6 @@ package android.net { method public int getUid(); } - public class DhcpInfo implements android.os.Parcelable { - ctor public DhcpInfo(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR; - field public int dns1; - field public int dns2; - field public int gateway; - field public int ipAddress; - field public int leaseDuration; - field public int netmask; - field public int serverAddress; - } - - public final class DnsResolver { - method @NonNull public static android.net.DnsResolver getInstance(); - method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); - method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); - method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); - method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); - field public static final int CLASS_IN = 1; // 0x1 - field public static final int ERROR_PARSE = 0; // 0x0 - field public static final int ERROR_SYSTEM = 1; // 0x1 - field public static final int FLAG_EMPTY = 0; // 0x0 - field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 - field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 - field public static final int FLAG_NO_RETRY = 1; // 0x1 - field public static final int TYPE_A = 1; // 0x1 - field public static final int TYPE_AAAA = 28; // 0x1c - } - - public static interface DnsResolver.Callback<T> { - method public void onAnswer(@NonNull T, int); - method public void onError(@NonNull android.net.DnsResolver.DnsException); - } - - public static class DnsResolver.DnsException extends java.lang.Exception { - field public final int code; - } - public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile { method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms(); method public int getMaxMtu(); @@ -26048,21 +25859,6 @@ package android.net { method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo); } - public class InetAddresses { - method public static boolean isNumericAddress(@NonNull String); - method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); - } - - public final class IpPrefix implements android.os.Parcelable { - method public boolean contains(@NonNull java.net.InetAddress); - method public int describeContents(); - method @NonNull public java.net.InetAddress getAddress(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method @NonNull public byte[] getRawAddress(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; - } - public final class IpSecAlgorithm implements android.os.Parcelable { ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]); ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int); @@ -26131,45 +25927,6 @@ package android.net { method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int); } - public class LinkAddress implements android.os.Parcelable { - method public int describeContents(); - method public java.net.InetAddress getAddress(); - method public int getFlags(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method public int getScope(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR; - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(); - method public boolean addRoute(@NonNull android.net.RouteInfo); - method public void clear(); - method public int describeContents(); - method @Nullable public java.net.Inet4Address getDhcpServerAddress(); - method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @Nullable public String getInterfaceName(); - method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses(); - method public int getMtu(); - method @Nullable public android.net.IpPrefix getNat64Prefix(); - method @Nullable public String getPrivateDnsServerName(); - method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(); - method public boolean isPrivateDnsActive(); - method public boolean isWakeOnLanSupported(); - method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); - method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); - method public void setDomains(@Nullable String); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setInterfaceName(@Nullable String); - method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>); - method public void setMtu(int); - method public void setNat64Prefix(@Nullable android.net.IpPrefix); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR; - } - public class LocalServerSocket implements java.io.Closeable { ctor public LocalServerSocket(String) throws java.io.IOException; ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException; @@ -26225,24 +25982,6 @@ package android.net { enum_constant public static final android.net.LocalSocketAddress.Namespace RESERVED; } - public final class MacAddress implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); - method @NonNull public static android.net.MacAddress fromString(@NonNull String); - method public int getAddressType(); - method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); - method public boolean isLocallyAssigned(); - method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); - method @NonNull public byte[] toByteArray(); - method @NonNull public String toOuiString(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.net.MacAddress BROADCAST_ADDRESS; - field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR; - field public static final int TYPE_BROADCAST = 3; // 0x3 - field public static final int TYPE_MULTICAST = 2; // 0x2 - field public static final int TYPE_UNICAST = 1; // 0x1 - } - public class MailTo { method public String getBody(); method public String getCc(); @@ -26254,138 +25993,6 @@ package android.net { field public static final String MAILTO_SCHEME = "mailto:"; } - public class Network implements android.os.Parcelable { - method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; - method public void bindSocket(java.net.Socket) throws java.io.IOException; - method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; - method public int describeContents(); - method public static android.net.Network fromNetworkHandle(long); - method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; - method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; - method public long getNetworkHandle(); - method public javax.net.SocketFactory getSocketFactory(); - method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; - method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR; - } - - public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(); - ctor public NetworkCapabilities(android.net.NetworkCapabilities); - method public int describeContents(); - method public int getLinkDownstreamBandwidthKbps(); - method public int getLinkUpstreamBandwidthKbps(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method public int getOwnerUid(); - method public int getSignalStrength(); - method @Nullable public android.net.TransportInfo getTransportInfo(); - method public boolean hasCapability(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; - field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 - field public static final int NET_CAPABILITY_CBS = 5; // 0x5 - field public static final int NET_CAPABILITY_DUN = 2; // 0x2 - field public static final int NET_CAPABILITY_EIMS = 10; // 0xa - field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 - field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 - field public static final int NET_CAPABILITY_IA = 7; // 0x7 - field public static final int NET_CAPABILITY_IMS = 4; // 0x4 - field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc - field public static final int NET_CAPABILITY_MCX = 23; // 0x17 - field public static final int NET_CAPABILITY_MMS = 0; // 0x0 - field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 - field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb - field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd - field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 - field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 - field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf - field public static final int NET_CAPABILITY_RCS = 8; // 0x8 - field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 - field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 - field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe - field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 - field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 - field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 - field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 - field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 - field public static final int TRANSPORT_CELLULAR = 0; // 0x0 - field public static final int TRANSPORT_ETHERNET = 3; // 0x3 - field public static final int TRANSPORT_LOWPAN = 6; // 0x6 - field public static final int TRANSPORT_VPN = 4; // 0x4 - field public static final int TRANSPORT_WIFI = 1; // 0x1 - field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 - } - - @Deprecated public class NetworkInfo implements android.os.Parcelable { - ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); - method @Deprecated public int describeContents(); - method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); - method @Deprecated public String getExtraInfo(); - method @Deprecated public String getReason(); - method @Deprecated public android.net.NetworkInfo.State getState(); - method @Deprecated public int getSubtype(); - method @Deprecated public String getSubtypeName(); - method @Deprecated public int getType(); - method @Deprecated public String getTypeName(); - method @Deprecated public boolean isAvailable(); - method @Deprecated public boolean isConnected(); - method @Deprecated public boolean isConnectedOrConnecting(); - method @Deprecated public boolean isFailover(); - method @Deprecated public boolean isRoaming(); - method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); - method @Deprecated public void writeToParcel(android.os.Parcel, int); - field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; - } - - @Deprecated public enum NetworkInfo.DetailedState { - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; - } - - @Deprecated public enum NetworkInfo.State { - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; - } - - public class NetworkRequest implements android.os.Parcelable { - method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); - method public int describeContents(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method public boolean hasCapability(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR; - } - - public static class NetworkRequest.Builder { - ctor public NetworkRequest.Builder(); - method public android.net.NetworkRequest.Builder addCapability(int); - method public android.net.NetworkRequest.Builder addTransportType(int); - method public android.net.NetworkRequest build(); - method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); - method public android.net.NetworkRequest.Builder removeCapability(int); - method public android.net.NetworkRequest.Builder removeTransportType(int); - method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); - method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); - } - public abstract class NetworkSpecifier { ctor public NetworkSpecifier(); } @@ -26402,44 +26009,6 @@ package android.net { field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6 } - public final class Proxy { - ctor public Proxy(); - method @Deprecated public static String getDefaultHost(); - method @Deprecated public static int getDefaultPort(); - method @Deprecated public static String getHost(android.content.Context); - method @Deprecated public static int getPort(android.content.Context); - field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - } - - public class ProxyInfo implements android.os.Parcelable { - ctor public ProxyInfo(@Nullable android.net.ProxyInfo); - method public static android.net.ProxyInfo buildDirectProxy(String, int); - method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>); - method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); - method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); - method public int describeContents(); - method public String[] getExclusionList(); - method public String getHost(); - method public android.net.Uri getPacFileUrl(); - method public int getPort(); - method public boolean isValid(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR; - } - - public final class RouteInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.IpPrefix getDestination(); - method @Nullable public java.net.InetAddress getGateway(); - method @Nullable public String getInterface(); - method public boolean hasGateway(); - method public boolean isDefaultRoute(); - method public boolean matches(java.net.InetAddress); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR; - } - @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor @Deprecated public SSLCertificateSocketFactory(int); method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; @@ -26465,30 +26034,6 @@ package android.net { ctor public SSLSessionCache(android.content.Context); } - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - method public final void close(); - method public final void start(@IntRange(from=0xa, to=0xe10) int); - method public final void stop(); - field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 - field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 - field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 - field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 - field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 - } - - public static class SocketKeepalive.Callback { - ctor public SocketKeepalive.Callback(); - method public void onDataReceived(); - method public void onError(int); - method public void onStarted(); - method public void onStopped(); - } - public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { method public int describeContents(); method public int getSubscriptionId(); @@ -26546,9 +26091,6 @@ package android.net { field public static final int UNSUPPORTED = -1; // 0xffffffff } - public interface TransportInfo { - } - public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable { method public abstract android.net.Uri.Builder buildUpon(); method public int compareTo(android.net.Uri); @@ -30655,8 +30197,8 @@ package android.os { } public final class BugreportManager { - method public void cancelBugreport(); - method public void startConnectivityBugreport(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); + method @WorkerThread public void cancelBugreport(); + method @WorkerThread public void startConnectivityBugreport(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public abstract static class BugreportManager.BugreportCallback { @@ -31921,10 +31463,10 @@ package android.os { method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle); method @WorkerThread public android.os.Bundle getApplicationRestrictions(String); method public long getSerialNumberForUser(android.os.UserHandle); - method @RequiresPermission("android.permission.MANAGE_USERS") public int getUserCount(); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount(); method public long getUserCreationTime(android.os.UserHandle); method public android.os.UserHandle getUserForSerialNumber(long); - method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional=true) public String getUserName(); + method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName(); method public java.util.List<android.os.UserHandle> getUserProfiles(); method public android.os.Bundle getUserRestrictions(); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); @@ -35118,6 +34660,7 @@ package android.provider { field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS"; field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS"; field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; + field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM"; field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE"; field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; @@ -37162,8 +36705,10 @@ package android.security { method @NonNull public static android.content.Intent createInstallIntent(); method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy); method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; + method @NonNull public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException; method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String); + method public static boolean isCredentialManagementApp(@NonNull android.content.Context); method public static boolean isKeyAlgorithmSupported(@NonNull String); field public static final String ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED"; field public static final String ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED"; @@ -40243,7 +39788,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); - method public boolean hasCompanionInCallServiceAccess(); + method public boolean hasManageOngoingCallsPermission(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); @@ -40870,6 +40415,7 @@ package android.telephony { } public static final class CarrierConfigManager.Apn { + field @Deprecated public static final String KEY_PREFIX = "apn."; field public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING = "apn.settings_default_protocol_string"; field public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING = "apn.settings_default_roaming_protocol_string"; field public static final String PROTOCOL_IPV4 = "IP"; @@ -41234,6 +40780,7 @@ package android.telephony { field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41 + field public static final int ALL_MATCHING_RULES_FAILED = 2254; // 0x8ce field public static final int APN_DISABLED = 2045; // 0x7fd field public static final int APN_DISALLOWED_ON_ROAMING = 2059; // 0x80b field public static final int APN_MISMATCH = 2054; // 0x806 @@ -41383,6 +40930,7 @@ package android.telephony { field public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 2117; // 0x845 field public static final int LTE_THROTTLING_NOT_REQUIRED = 2127; // 0x84f field public static final int MAC_FAILURE = 2183; // 0x887 + field public static final int MATCH_ALL_RULE_NOT_ALLOWED = 2253; // 0x8cd field public static final int MAXIMIUM_NSAPIS_EXCEEDED = 2157; // 0x86d field public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166; // 0x876 field public static final int MAX_ACCESS_PROBE = 2079; // 0x81f @@ -42120,7 +41668,6 @@ package android.telephony { public final class SignalStrengthUpdateRequest implements android.os.Parcelable { method public int describeContents(); - method @NonNull public android.os.IBinder getLiveToken(); method @NonNull public java.util.Collection<android.telephony.SignalThresholdInfo> getSignalThresholdInfos(); method public boolean isReportingRequestedWhileIdle(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -42168,6 +41715,7 @@ package android.telephony { method @NonNull public android.telephony.SmsManager createForSubscriptionId(int); method public java.util.ArrayList<java.lang.String> divideMessage(String); method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent); + method public void downloadMultimediaMessage(@NonNull android.content.Context, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent, long); method @NonNull public android.os.Bundle getCarrierConfigValues(); method @Deprecated public static android.telephony.SmsManager getDefault(); method public static int getDefaultSmsSubscriptionId(); @@ -42179,6 +41727,7 @@ package android.telephony { method public void injectSmsPdu(byte[], String, android.app.PendingIntent); method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent); method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent); + method public void sendMultimediaMessage(@NonNull android.content.Context, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent, long); method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>); method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, long); method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String, @Nullable String); @@ -46696,7 +46245,6 @@ package android.view { method public long getAppVsyncOffsetNanos(); method public void getCurrentSizeRange(android.graphics.Point, android.graphics.Point); method @Nullable public android.view.DisplayCutout getCutout(); - method @Nullable public android.hardware.display.DeviceProductInfo getDeviceProductInfo(); method public int getDisplayId(); method public int getFlags(); method public android.view.Display.HdrCapabilities getHdrCapabilities(); @@ -48020,16 +47568,47 @@ package android.view { method public void onScaleEnd(android.view.ScaleGestureDetector); } + @UiThread public interface ScrollCaptureCallback { + method public void onScrollCaptureEnd(@NonNull Runnable); + method public void onScrollCaptureImageRequest(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull android.graphics.Rect, @NonNull java.util.function.Consumer<android.graphics.Rect>); + method public void onScrollCaptureSearch(@NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.graphics.Rect>); + method public void onScrollCaptureStart(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull Runnable); + } + + public class ScrollCaptureSession { + ctor public ScrollCaptureSession(@NonNull android.view.Surface, @NonNull android.graphics.Rect, @NonNull android.graphics.Point); + method @NonNull public android.graphics.Point getPositionInWindow(); + method @NonNull public android.graphics.Rect getScrollBounds(); + method @NonNull public android.view.Surface getSurface(); + } + + public final class ScrollCaptureTarget { + ctor public ScrollCaptureTarget(@NonNull android.view.View, @NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull android.view.ScrollCaptureCallback); + method @NonNull public android.view.ScrollCaptureCallback getCallback(); + method @NonNull public android.view.View getContainingView(); + method public int getHint(); + method @NonNull public android.graphics.Rect getLocalVisibleRect(); + method @NonNull public android.graphics.Point getPositionInWindow(); + method @Nullable public android.graphics.Rect getScrollBounds(); + method public void setScrollBounds(@Nullable android.graphics.Rect); + method @UiThread public void updatePositionInWindow(); + } + public class SearchEvent { ctor public SearchEvent(android.view.InputDevice); method public android.view.InputDevice getInputDevice(); } public class SoundEffectConstants { + method public static int getConstantForFocusDirection(int, boolean); method public static int getContantForFocusDirection(int); field public static final int CLICK = 0; // 0x0 field public static final int NAVIGATION_DOWN = 4; // 0x4 field public static final int NAVIGATION_LEFT = 1; // 0x1 + field public static final int NAVIGATION_REPEAT_DOWN = 8; // 0x8 + field public static final int NAVIGATION_REPEAT_LEFT = 5; // 0x5 + field public static final int NAVIGATION_REPEAT_RIGHT = 7; // 0x7 + field public static final int NAVIGATION_REPEAT_UP = 6; // 0x6 field public static final int NAVIGATION_RIGHT = 3; // 0x3 field public static final int NAVIGATION_UP = 2; // 0x2 } @@ -48341,6 +47920,7 @@ package android.view { method public void dispatchProvideStructure(android.view.ViewStructure); method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>); + method public void dispatchScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>); method protected void dispatchSetActivated(boolean); method protected void dispatchSetPressed(boolean); method protected void dispatchSetSelected(boolean); @@ -48500,6 +48080,7 @@ package android.view { method public int getScrollBarFadeDuration(); method public int getScrollBarSize(); method public int getScrollBarStyle(); + method public int getScrollCaptureHint(); method public int getScrollIndicators(); method public final int getScrollX(); method public final int getScrollY(); @@ -48668,6 +48249,7 @@ package android.view { method public void onRtlPropertiesChanged(int); method @CallSuper @Nullable protected android.os.Parcelable onSaveInstanceState(); method public void onScreenStateChanged(int); + method public void onScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>); method protected void onScrollChanged(int, int, int, int); method protected boolean onSetAlpha(int); method protected void onSizeChanged(int, int, int, int); @@ -48850,6 +48432,8 @@ package android.view { method public void setScrollBarFadeDuration(int); method public void setScrollBarSize(int); method public void setScrollBarStyle(int); + method public final void setScrollCaptureCallback(@Nullable android.view.ScrollCaptureCallback); + method public void setScrollCaptureHint(int); method public void setScrollContainer(boolean); method public void setScrollIndicators(int); method public void setScrollIndicators(int, int); @@ -49028,6 +48612,10 @@ package android.view { field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1 field public static final int SCROLL_AXIS_NONE = 0; // 0x0 field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2 + field public static final int SCROLL_CAPTURE_HINT_AUTO = 0; // 0x0 + field public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 1; // 0x1 + field public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 4; // 0x4 + field public static final int SCROLL_CAPTURE_HINT_INCLUDE = 2; // 0x2 field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2 field public static final int SCROLL_INDICATOR_END = 32; // 0x20 field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4 @@ -49812,6 +49400,7 @@ package android.view { method public abstract boolean performContextMenuIdentifierAction(int, int); method public abstract boolean performPanelIdentifierAction(int, int, int); method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int); + method public void registerScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback); method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener); method public boolean requestFeature(int); method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int); @@ -49891,6 +49480,7 @@ package android.view { method public abstract void takeKeyEvents(boolean); method public abstract void takeSurface(android.view.SurfaceHolder.Callback2); method public abstract void togglePanel(int, android.view.KeyEvent); + method public void unregisterScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback); field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0 field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2 field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1 @@ -53383,6 +52973,7 @@ package android.widget { method public int getCheckedItemPosition(); method public android.util.SparseBooleanArray getCheckedItemPositions(); method public int getChoiceMode(); + method public int getEdgeEffectType(); method public int getListPaddingBottom(); method public int getListPaddingLeft(); method public int getListPaddingRight(); @@ -53424,6 +53015,7 @@ package android.widget { method public void setChoiceMode(int); method public void setDrawSelectorOnTop(boolean); method public void setEdgeEffectColor(@ColorInt int); + method public void setEdgeEffectType(int); method public void setFastScrollAlwaysVisible(boolean); method public void setFastScrollEnabled(boolean); method public void setFastScrollStyle(int); @@ -53714,11 +53306,27 @@ package android.widget { ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet); ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int); ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int, int); + method @Deprecated @Nullable public android.graphics.BlendMode getDialTintBlendMode(); + method @Deprecated @Nullable public android.content.res.ColorStateList getDialTintList(); + method @Deprecated @Nullable public android.graphics.BlendMode getHourHandTintBlendMode(); + method @Deprecated @Nullable public android.content.res.ColorStateList getHourHandTintList(); + method @Deprecated @Nullable public android.graphics.BlendMode getMinuteHandTintBlendMode(); + method @Deprecated @Nullable public android.content.res.ColorStateList getMinuteHandTintList(); + method @Deprecated @Nullable public android.graphics.BlendMode getSecondHandTintBlendMode(); + method @Deprecated @Nullable public android.content.res.ColorStateList getSecondHandTintList(); method @Deprecated @Nullable public String getTimeZone(); method @Deprecated public void setDial(@NonNull android.graphics.drawable.Icon); + method @Deprecated public void setDialTintBlendMode(@Nullable android.graphics.BlendMode); + method @Deprecated public void setDialTintList(@Nullable android.content.res.ColorStateList); method @Deprecated public void setHourHand(@NonNull android.graphics.drawable.Icon); + method @Deprecated public void setHourHandTintBlendMode(@Nullable android.graphics.BlendMode); + method @Deprecated public void setHourHandTintList(@Nullable android.content.res.ColorStateList); method @Deprecated public void setMinuteHand(@NonNull android.graphics.drawable.Icon); + method @Deprecated public void setMinuteHandTintBlendMode(@Nullable android.graphics.BlendMode); + method @Deprecated public void setMinuteHandTintList(@Nullable android.content.res.ColorStateList); method @Deprecated public void setSecondHand(@Nullable android.graphics.drawable.Icon); + method @Deprecated public void setSecondHandTintBlendMode(@Nullable android.graphics.BlendMode); + method @Deprecated public void setSecondHandTintList(@Nullable android.content.res.ColorStateList); method @Deprecated public void setTimeZone(@Nullable String); } @@ -54416,6 +54024,7 @@ package android.widget { method public boolean executeKeyEvent(android.view.KeyEvent); method public void fling(int); method public boolean fullScroll(int); + method public int getEdgeEffectType(); method @ColorInt public int getLeftEdgeEffectColor(); method public int getMaxScrollAmount(); method @ColorInt public int getRightEdgeEffectColor(); @@ -54423,6 +54032,7 @@ package android.widget { method public boolean isSmoothScrollingEnabled(); method public boolean pageScroll(int); method public void setEdgeEffectColor(@ColorInt int); + method public void setEdgeEffectType(int); method public void setFillViewport(boolean); method public void setLeftEdgeEffectColor(@ColorInt int); method public void setRightEdgeEffectColor(@ColorInt int); @@ -55276,6 +54886,7 @@ package android.widget { method public void fling(int); method public boolean fullScroll(int); method @ColorInt public int getBottomEdgeEffectColor(); + method public int getEdgeEffectType(); method public int getMaxScrollAmount(); method @ColorInt public int getTopEdgeEffectColor(); method public boolean isFillViewport(); @@ -55284,6 +54895,7 @@ package android.widget { method public void scrollToDescendant(@NonNull android.view.View); method public void setBottomEdgeEffectColor(@ColorInt int); method public void setEdgeEffectColor(@ColorInt int); + method public void setEdgeEffectType(int); method public void setFillViewport(boolean); method public void setSmoothScrollingEnabled(boolean); method public void setTopEdgeEffectColor(@ColorInt int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 5dc1cd7a3173..e6f0e4804655 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -163,61 +163,12 @@ package android.media.session { package android.net { - public final class ConnectivityFrameworkInitializer { - method public static void registerServiceWrappers(); - } - - public class ConnectivityManager { - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); - } - public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { method public int getResourceId(); } - public final class NetworkAgentConfig implements android.os.Parcelable { - method @Nullable public String getSubscriberId(); - } - - public static final class NetworkAgentConfig.Builder { - method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - field public static final int TRANSPORT_TEST = 7; // 0x7 - } - - public final class Proxy { - method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); - } - - public final class TcpRepairWindow { - ctor public TcpRepairWindow(int, int, int, int, int, int); - field public final int maxWindow; - field public final int rcvWnd; - field public final int rcvWndScale; - field public final int rcvWup; - field public final int sndWl1; - field public final int sndWnd; - } - - public final class TestNetworkInterface implements android.os.Parcelable { - ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); - method public int describeContents(); - method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); - method @NonNull public String getInterfaceName(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR; - } - - public class TestNetworkManager { - method @NonNull public android.net.TestNetworkInterface createTapInterface(); - method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>); - method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); - method public void teardownTestNetwork(@NonNull android.net.Network); - field public static final String TEST_TAP_PREFIX = "testtap"; + public class NetworkWatchlistManager { + method @Nullable public byte[] getWatchlistConfigHash(); } public final class UnderlyingNetworkInfo implements android.os.Parcelable { @@ -230,14 +181,6 @@ 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/removed.txt b/core/api/removed.txt index 990388a54c85..a99178d51d77 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -241,12 +241,6 @@ package android.media.tv { package android.net { - public class ConnectivityManager { - method @Deprecated public boolean requestRouteToHost(int, int); - method @Deprecated public int startUsingNetworkFeature(int, String); - method @Deprecated public int stopUsingNetworkFeature(int, String); - } - @Deprecated public class NetworkBadging { method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); field public static final int BADGING_4K = 30; // 0x1e diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fdb925e9c7a9..400c3554b144 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -27,6 +27,7 @@ package android { field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; 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 BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; 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"; @@ -176,6 +177,7 @@ package android { field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; field public static final String OBSERVE_NETWORK_POLICY = "android.permission.OBSERVE_NETWORK_POLICY"; field public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS"; + field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY"; field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; @@ -214,6 +216,7 @@ package android { field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String RECOVERY = "android.permission.RECOVERY"; field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; @@ -352,6 +355,7 @@ package android { field public static final int config_systemContacts = 17039403; // 0x104002b field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a + field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e } public static final class R.style { @@ -641,8 +645,9 @@ package android.app { method public static android.app.BroadcastOptions makeBasic(); method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean); method public void setDontSendToRestrictedApps(boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long); - method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long); + method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long); method public android.os.Bundle toBundle(); } @@ -884,7 +889,7 @@ package android.app.admin { public class DevicePolicyManager { 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(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); @@ -1826,7 +1831,7 @@ package android.bluetooth { method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public android.bluetooth.BufferConstraints getBufferConstraints(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getDynamicBufferSupport(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setBufferMillis(int, int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setBufferLengthMillis(int, int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; // 0x1 field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; // 0x2 @@ -1942,6 +1947,10 @@ package android.bluetooth { field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.SEND_SMS) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent); + } + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); @@ -1974,6 +1983,7 @@ package android.bluetooth { field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0 field public static final int CONNECTION_POLICY_UNKNOWN = -1; // 0xffffffff field public static final int HEADSET_CLIENT = 16; // 0x10 + field public static final int MAP_CLIENT = 18; // 0x12 field public static final int PAN = 5; // 0x5 field public static final int PBAP_CLIENT = 17; // 0x11 field @Deprecated public static final int PRIORITY_OFF = 0; // 0x0 @@ -2027,7 +2037,7 @@ package android.bluetooth { public final class BufferConstraints implements android.os.Parcelable { ctor public BufferConstraints(@NonNull java.util.List<android.bluetooth.BufferConstraint>); method public int describeContents(); - method @Nullable public android.bluetooth.BufferConstraint getCodec(int); + method @Nullable public android.bluetooth.BufferConstraint forCodec(int); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BUFFER_CODEC_MAX_NUM = 32; // 0x20 field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraints> CREATOR; @@ -2456,10 +2466,10 @@ package android.content.pm { } public static class PackageInstaller.Session implements java.io.Closeable { - method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]); + method public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender); - method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams(); - method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String); + method @Nullable public android.content.pm.DataLoaderParams getDataLoaderParams(); + method public void removeFile(int, @NonNull String); } public static class PackageInstaller.SessionInfo implements android.os.Parcelable { @@ -2480,7 +2490,7 @@ package android.content.pm { public static class PackageInstaller.SessionParams implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean); method @Deprecated public void setAllowDowngrade(boolean); - method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams); + method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams); method public void setDontKillApp(boolean); method public void setEnableRollback(boolean); method public void setEnableRollback(boolean, int); @@ -2545,10 +2555,12 @@ package android.content.pm { field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; + field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg"; @@ -2560,7 +2572,6 @@ package android.content.pm { field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800 - field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000 field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000 field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 @@ -2746,6 +2757,15 @@ package android.content.pm.permission { package android.content.pm.verify.domain { + public final class DomainOwner implements android.os.Parcelable { + ctor public DomainOwner(@NonNull String, boolean); + method public int describeContents(); + method @NonNull public String getPackageName(); + method public boolean isOverrideable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainOwner> CREATOR; + } + public final class DomainVerificationInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); @@ -2758,6 +2778,7 @@ package android.content.pm.verify.domain { public interface DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames(); method public static boolean isStateModifiable(int); method public static boolean isStateVerified(int); @@ -2779,13 +2800,16 @@ package android.content.pm.verify.domain { public final class DomainVerificationUserSelection implements android.os.Parcelable { method public int describeContents(); - method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap(); + method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); method @NonNull public java.util.UUID getIdentifier(); method @NonNull public String getPackageName(); method @NonNull public android.os.UserHandle getUser(); method @NonNull public boolean isLinkHandlingAllowed(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR; + field public static final int DOMAIN_STATE_NONE = 0; // 0x0 + field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 + field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 } } @@ -2869,7 +2893,7 @@ package android.graphics.fonts { } public class FontManager { - method @Nullable public android.text.FontConfig getFontConfig(); + method @NonNull public android.text.FontConfig getFontConfig(); 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); @@ -2903,6 +2927,22 @@ package android.hardware { method public boolean injectSensorData(android.hardware.Sensor, float[], int, long); } + public final class SensorPrivacyManager { + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 + } + } package android.hardware.biometrics { @@ -4683,6 +4723,7 @@ package android.location { } public class LocationManager { + method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void addProviderRequestChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.provider.ProviderRequest.ChangedListener); method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>); method @Nullable public String getExtraLocationControllerPackage(); @@ -4697,7 +4738,7 @@ package android.location { method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String, @Nullable String); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerProviderRequestListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.provider.ProviderRequest.Listener); + method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void removeProviderRequestChangedListener(@NonNull android.location.provider.ProviderRequest.ChangedListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent); @@ -4706,7 +4747,6 @@ package android.location { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void unregisterProviderRequestListener(@NonNull android.location.provider.ProviderRequest.Listener); } public final class LocationRequest implements android.os.Parcelable { @@ -4856,7 +4896,7 @@ package android.location.provider { method @NonNull public android.location.provider.ProviderRequest.Builder setWorkSource(@NonNull android.os.WorkSource); } - public static interface ProviderRequest.Listener { + public static interface ProviderRequest.ChangedListener { method public void onProviderRequestChanged(@NonNull String, @NonNull android.location.provider.ProviderRequest); } @@ -5067,7 +5107,7 @@ package android.media { } public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation { - method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener, @Nullable android.os.Handler); + method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener); } public static interface MediaPlayer.OnRtpRxNoticeListener { @@ -7058,102 +7098,6 @@ package android.metrics { package android.net { - public class CaptivePortal implements android.os.Parcelable { - method public void logEvent(int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); - method public void useNetwork(); - field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 - field public static final int APP_RETURN_DISMISSED = 0; // 0x0 - field public static final int APP_RETURN_UNWANTED = 1; // 0x1 - field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 - } - - public final class CaptivePortalData implements android.os.Parcelable { - method public int describeContents(); - method public long getByteLimit(); - method public long getExpiryTimeMillis(); - method public long getRefreshTimeMillis(); - method @Nullable public android.net.Uri getUserPortalUrl(); - method public int getUserPortalUrlSource(); - method @Nullable public String getVenueFriendlyName(); - method @Nullable public android.net.Uri getVenueInfoUrl(); - method public int getVenueInfoUrlSource(); - method public boolean isCaptive(); - method public boolean isSessionExtendable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 - field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; - } - - public static class CaptivePortalData.Builder { - ctor public CaptivePortalData.Builder(); - ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); - method @NonNull public android.net.CaptivePortalData build(); - method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); - method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); - method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); - } - - public class ConnectivityManager { - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); - method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); - method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); - method public void unregisterQosCallback(@NonNull android.net.QosCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); - field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; - field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd - field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb - field public static final int TYPE_NONE = -1; // 0xffffffff - field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 - field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd - } - - public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { - method public void onComplete(); - } - - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { - ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); - method @Deprecated public void onTetheringFailed(); - method @Deprecated public void onTetheringStarted(); - } - - @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { - method @Deprecated public void onTetheringEntitlementResult(int); - } - - @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { - ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); - method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); - } - public class DnsResolverServiceManager { method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context); } @@ -7171,48 +7115,6 @@ package android.net { method public void release(); } - public final class InvalidPacketException extends java.lang.Exception { - ctor public InvalidPacketException(int); - method public int getError(); - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - } - - public final class IpConfiguration implements android.os.Parcelable { - ctor public IpConfiguration(); - ctor public IpConfiguration(@NonNull android.net.IpConfiguration); - method public int describeContents(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); - method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); - method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); - method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); - method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR; - } - - public enum IpConfiguration.IpAssignment { - enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; - enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; - enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; - } - - public enum IpConfiguration.ProxySettings { - enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; - enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; - enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; - enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; - } - - public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - ctor public IpPrefix(@NonNull String); - } - public final class IpSecManager { method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; @@ -7230,68 +7132,6 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; } - public class KeepalivePacketData { - ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; - method @NonNull public java.net.InetAddress getDstAddress(); - method public int getDstPort(); - method @NonNull public byte[] getPacket(); - method @NonNull public java.net.InetAddress getSrcAddress(); - method public int getSrcPort(); - } - - public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - ctor public LinkAddress(@NonNull String); - ctor public LinkAddress(@NonNull String, int, int); - method public long getDeprecationTime(); - method public long getExpirationTime(); - method public boolean isGlobalPreferred(); - method public boolean isIpv4(); - method public boolean isIpv6(); - method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); - field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL - field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(@Nullable android.net.LinkProperties); - ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); - method public boolean addDnsServer(@NonNull java.net.InetAddress); - method public boolean addLinkAddress(@NonNull android.net.LinkAddress); - method public boolean addPcscfServer(@NonNull java.net.InetAddress); - method @NonNull public java.util.List<java.net.InetAddress> getAddresses(); - method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames(); - method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses(); - method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes(); - method @Nullable public android.net.Uri getCaptivePortalApiUrl(); - method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); - method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); - method @Nullable public String getTcpBufferSizes(); - method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); - method public boolean hasGlobalIpv6Address(); - method public boolean hasIpv4Address(); - method public boolean hasIpv4DefaultRoute(); - method public boolean hasIpv4DnsServer(); - method public boolean hasIpv6DefaultRoute(); - method public boolean hasIpv6DnsServer(); - method public boolean isIpv4Provisioned(); - method public boolean isIpv6Provisioned(); - method public boolean isProvisioned(); - method public boolean isReachable(@NonNull java.net.InetAddress); - method public boolean removeDnsServer(@NonNull java.net.InetAddress); - method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); - method public boolean removeRoute(@NonNull android.net.RouteInfo); - method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); - method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); - method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); - method public void setPrivateDnsServerName(@Nullable String); - method public void setTcpBufferSizes(@Nullable String); - method public void setUsePrivateDns(boolean); - method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); - } - public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { ctor public MatchAllNetworkSpecifier(); method public int describeContents(); @@ -7299,104 +7139,6 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } - public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; - } - - public class Network implements android.os.Parcelable { - ctor public Network(@NonNull android.net.Network); - method public int getNetId(); - method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); - } - - public abstract class NetworkAgent { - ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); - method @Nullable public android.net.Network getNetwork(); - method public void markConnected(); - method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); - method public void onAutomaticReconnectDisabled(); - method public void onNetworkUnwanted(); - method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); - method public void onQosCallbackUnregistered(int); - method public void onRemoveKeepalivePacketFilter(int); - method public void onSaveAcceptUnvalidated(boolean); - method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); - method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); - method public void onStopSocketKeepalive(int); - method public void onValidationStatus(int, @Nullable android.net.Uri); - method @NonNull public android.net.Network register(); - method public final void sendLinkProperties(@NonNull android.net.LinkProperties); - method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); - method public final void sendNetworkScore(@IntRange(from=0, to=99) int); - method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); - method public final void sendQosSessionLost(int, int); - method public final void sendSocketKeepaliveEvent(int, int); - method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); - method public void unregister(); - field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 - field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 - } - - public final class NetworkAgentConfig implements android.os.Parcelable { - method public int describeContents(); - method public int getLegacyType(); - method @NonNull public String getLegacyTypeName(); - method public boolean isExplicitlySelected(); - method public boolean isPartialConnectivityAcceptable(); - method public boolean isUnvalidatedConnectivityAcceptable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; - } - - public static final class NetworkAgentConfig.Builder { - ctor public NetworkAgentConfig.Builder(); - method @NonNull public android.net.NetworkAgentConfig build(); - method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); - method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); - 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 - field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a - field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 - field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b - } - - public static final class NetworkCapabilities.Builder { - ctor public NetworkCapabilities.Builder(); - ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); - 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[]); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); - method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); - } - public class NetworkKey implements android.os.Parcelable { ctor public NetworkKey(android.net.WifiKey); method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); @@ -7408,15 +7150,6 @@ package android.net { field public final android.net.WifiKey wifiKey; } - public class NetworkProvider { - ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); - method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); - method public int getProviderId(); - method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); - method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); - field public static final int ID_NONE = -1; // 0xffffffff - } - public abstract class NetworkRecommendationProvider { ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor); method public final android.os.IBinder getBinder(); @@ -7426,15 +7159,6 @@ package android.net { public class NetworkReleasedException extends java.lang.Exception { } - public class NetworkRequest implements android.os.Parcelable { - method @Nullable public String getRequestorPackageName(); - method public int getRequestorUid(); - } - - public static class NetworkRequest.Builder { - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); - } - public class NetworkScoreManager { method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; @@ -7559,16 +7283,6 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR; } - public final class RouteInfo implements android.os.Parcelable { - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); - method public int getMtu(); - method public int getType(); - field public static final int RTN_THROW = 9; // 0x9 - field public static final int RTN_UNICAST = 1; // 0x1 - field public static final int RTN_UNREACHABLE = 7; // 0x7 - } - public class RssiCurve implements android.os.Parcelable { ctor public RssiCurve(int, int, byte[]); ctor public RssiCurve(int, int, byte[], int); @@ -7600,53 +7314,12 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - field public static final int SUCCESS = 0; // 0x0 - } - public class SocketLocalAddressChangedException extends java.lang.Exception { } public class SocketNotBoundException extends java.lang.Exception { } - public final class StaticIpConfiguration implements android.os.Parcelable { - ctor public StaticIpConfiguration(); - ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - method public void addDnsServer(@NonNull java.net.InetAddress); - method public void clear(); - method public int describeContents(); - method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public java.net.InetAddress getGateway(); - method @Nullable public android.net.LinkAddress getIpAddress(); - method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; - } - - public static final class StaticIpConfiguration.Builder { - ctor public StaticIpConfiguration.Builder(); - method @NonNull public android.net.StaticIpConfiguration build(); - method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>); - method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); - method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); - method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); - } - - public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR; - field public final int ipTos; - field public final int ipTtl; - field public final int tcpAck; - field public final int tcpSeq; - field public final int tcpWindow; - field public final int tcpWindowScale; - } - public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); @@ -7659,11 +7332,6 @@ package android.net { field public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = -256; // 0xffffff00 } - public interface TransportInfo { - method public default boolean hasLocationSensitiveFields(); - method @NonNull public default android.net.TransportInfo makeCopy(boolean); - } - public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable { method @NonNull public String toSafeString(); } @@ -7687,23 +7355,6 @@ package android.net { } -package android.net.apf { - - public final class ApfCapabilities implements android.os.Parcelable { - ctor public ApfCapabilities(int, int, int); - method public int describeContents(); - method public static boolean getApfDrop8023Frames(); - method @NonNull public static int[] getApfEtherTypeBlackList(); - method public boolean hasDataAccess(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; - field public final int apfPacketFormat; - field public final int apfVersionSupported; - field public final int maximumApfProgramSize; - } - -} - package android.net.metrics { @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { @@ -7898,15 +7549,24 @@ package android.net.sip { } -package android.net.util { +package android.net.vcn { - public final class SocketUtils { - method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; - method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; - method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); - method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); + public class VcnManager { + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties); + method public void removeVcnNetworkPolicyListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener); + } + + public static interface VcnManager.VcnNetworkPolicyListener { + method public void onPolicyChanged(); + } + + public final class VcnNetworkPolicyResult implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public boolean isTeardownRequested(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR; } } @@ -8258,7 +7918,7 @@ package android.os { public final class BugreportManager { method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence); - method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); + method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public final class BugreportParams { @@ -8580,11 +8240,18 @@ package android.os { method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String); + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long); + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable String); field public static final int EVENT_MMS = 2; // 0x2 field public static final int EVENT_SMS = 1; // 0x1 field public static final int EVENT_UNSPECIFIED = 0; // 0x0 + field public static final int REASON_ACTIVITY_RECOGNITION = 102; // 0x66 + field public static final int REASON_GEOFENCING = 100; // 0x64 + field public static final int REASON_OTHER = 1; // 0x1 + field public static final int REASON_PUSH_MESSAGING = 101; // 0x65 + field public static final int REASON_UNKNOWN = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 } @@ -8733,13 +8400,13 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException; method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles(); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getRestrictedProfileParent(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountType(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon(); method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle); @@ -9177,6 +8844,7 @@ package android.provider { field public static final String NAMESPACE_BIOMETRICS = "biometrics"; field public static final String NAMESPACE_BLOBSTORE = "blobstore"; field public static final String NAMESPACE_BLUETOOTH = "bluetooth"; + field public static final String NAMESPACE_CLIPBOARD = "clipboard"; field public static final String NAMESPACE_CONNECTIVITY = "connectivity"; field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture"; field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot"; @@ -9191,6 +8859,7 @@ package android.provider { field public static final String NAMESPACE_PERMISSIONS = "permissions"; field public static final String NAMESPACE_PRIVACY = "privacy"; field public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot"; + field public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness"; field public static final String NAMESPACE_ROLLBACK = "rollback"; field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot"; field public static final String NAMESPACE_RUNTIME = "runtime"; @@ -10735,7 +10404,7 @@ package android.telecom { method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle(); method @Nullable public final String getTelecomCallId(); method @Deprecated public void onAudioStateChanged(android.telecom.AudioState); - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(@NonNull android.telecom.Connection.CallFilteringCompletionInfo); method public final void resetConnectionTime(); method public void setCallDirection(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long); @@ -10751,6 +10420,16 @@ package android.telecom { field public static final int PROPERTY_REMOTELY_HOSTED = 2048; // 0x800 } + public static final class Connection.CallFilteringCompletionInfo implements android.os.Parcelable { + ctor public Connection.CallFilteringCompletionInfo(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, @Nullable android.content.ComponentName); + method public int describeContents(); + method @Nullable public android.telecom.CallScreeningService.CallResponse getCallResponse(); + method @Nullable public android.content.ComponentName getCallScreeningComponent(); + method public boolean isBlocked(); + method public boolean isInContacts(); + field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR; + } + public final class ConnectionRequest implements android.os.Parcelable { method @Nullable public String getTelecomCallId(); } @@ -10912,7 +10591,7 @@ package android.telecom { } public final class RemoteConnection { - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(@NonNull android.telecom.Connection.CallFilteringCompletionInfo); method @Deprecated public void setAudioState(android.telecom.AudioState); } @@ -12002,7 +11681,6 @@ package android.telephony { field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1 field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4 field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3 - field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED"; field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE"; field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 @@ -12227,6 +11905,7 @@ package android.telephony.data { method public long getRetryDurationMillis(); method @Nullable public android.telephony.data.SliceInfo getSliceInfo(); method @Deprecated public int getSuggestedRetryTime(); + method @NonNull public java.util.List<android.telephony.data.TrafficDescriptor> getTrafficDescriptors(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR; field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1 @@ -12262,6 +11941,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryDurationMillis(long); method @NonNull public android.telephony.data.DataCallResponse.Builder setSliceInfo(@Nullable android.telephony.data.SliceInfo); method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setSuggestedRetryTime(int); + method @NonNull public android.telephony.data.DataCallResponse.Builder setTrafficDescriptors(@NonNull java.util.List<android.telephony.data.TrafficDescriptor>); } public final class DataProfile implements android.os.Parcelable { @@ -12332,7 +12012,7 @@ package android.telephony.data { method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback); - method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @Nullable android.telephony.data.SliceInfo, @NonNull android.telephony.data.DataServiceCallback); + method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @Nullable android.telephony.data.SliceInfo, @Nullable android.telephony.data.TrafficDescriptor, boolean, @NonNull android.telephony.data.DataServiceCallback); } public class DataServiceCallback { @@ -12431,6 +12111,15 @@ package android.telephony.data { method @NonNull public android.telephony.data.ThrottleStatus.Builder setTransportType(int); } + public final class TrafficDescriptor implements android.os.Parcelable { + ctor public TrafficDescriptor(@Nullable String, @Nullable String); + method public int describeContents(); + method @Nullable public String getDnn(); + method @Nullable public String getOsAppId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.TrafficDescriptor> CREATOR; + } + } package android.telephony.euicc { @@ -14250,21 +13939,10 @@ package android.uwb { public final class UwbManager { method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos(); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getAngleOfArrivalSupport(); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerInitiatorSession(); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerResponderSession(); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxSimultaneousSessions(); method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.PersistableBundle getSpecificationInfo(); - method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.List<java.lang.Integer> getSupportedChannelNumbers(); - method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices(); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public boolean isRangingSupported(); method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback); - field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; // 0x2 - field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; // 0x3 - field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; // 0x4 - field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE = 1; // 0x1 } public static interface UwbManager.AdapterStateCallback { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a02320dbb70a..ca261cda0a56 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -5,14 +5,17 @@ package android { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; + field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final String CLEAR_FREEZE_PERIOD = "android.permission.CLEAR_FREEZE_PERIOD"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE"; + field public static final String FORCE_DEVICE_POLICY_MANAGER_LOGS = "android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES"; field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; @@ -28,6 +31,7 @@ package android { field public static final String QUERY_USERS = "android.permission.QUERY_USERS"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; @@ -91,6 +95,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener); method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName); method public long getTotalRam(); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessCapabilities(int); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessState(int); method public void holdLock(android.os.IBinder, int); method public static boolean isHighEndGfx(); method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener); @@ -106,7 +112,10 @@ package android.app { field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 + field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8 field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 + field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 + field public static final int PROCESS_STATE_TOP = 2; // 0x2 } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -384,7 +393,11 @@ package android.app.admin { public class DevicePolicyManager { method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); + method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); + method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); method public void forceUpdateUserSetupComplete(); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); @@ -392,10 +405,13 @@ package android.app.admin { method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); + method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName); method @NonNull public static String operationSafetyReasonToString(int); method @NonNull public static String operationToString(int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); + method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 @@ -580,6 +596,14 @@ package android.app.usage { } +package android.appwidget { + + public class AppWidgetManager { + method public void setBindAppWidgetPermission(@NonNull String, int, boolean); + } + +} + package android.bluetooth { public final class BluetoothClass implements android.os.Parcelable { @@ -696,8 +720,10 @@ package android.content.pm { method public void holdLock(android.os.IBinder, int); method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; + field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000 field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; @@ -724,6 +750,65 @@ package android.content.pm { ctor public ShortcutManager(android.content.Context); } + public class UserInfo implements android.os.Parcelable { + ctor public UserInfo(int, String, int); + ctor public UserInfo(int, String, String, int); + ctor public UserInfo(int, String, String, int, String); + ctor @Deprecated public UserInfo(); + ctor public UserInfo(android.content.pm.UserInfo); + method public boolean canHaveProfile(); + method public int describeContents(); + method public android.os.UserHandle getUserHandle(); + method public boolean isAdmin(); + method public boolean isDemo(); + method public boolean isEnabled(); + method public boolean isEphemeral(); + method public boolean isFull(); + method public boolean isGuest(); + method public boolean isInitialized(); + method public boolean isManagedProfile(); + method public boolean isPrimary(); + method public boolean isProfile(); + method public boolean isQuietModeEnabled(); + method public boolean isRestricted(); + method public boolean isSystemOnly(); + method public static boolean isSystemOnly(int); + method public boolean supportsSwitchTo(); + method public boolean supportsSwitchToByUser(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserInfo> CREATOR; + field public static final int FLAG_ADMIN = 2; // 0x2 + field @Deprecated public static final int FLAG_DEMO = 512; // 0x200 + field public static final int FLAG_DISABLED = 64; // 0x40 + field public static final int FLAG_EPHEMERAL = 256; // 0x100 + field public static final int FLAG_FULL = 1024; // 0x400 + field @Deprecated public static final int FLAG_GUEST = 4; // 0x4 + field public static final int FLAG_INITIALIZED = 16; // 0x10 + field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20 + field public static final int FLAG_PRIMARY = 1; // 0x1 + field public static final int FLAG_PROFILE = 4096; // 0x1000 + field public static final int FLAG_QUIET_MODE = 128; // 0x80 + field @Deprecated public static final int FLAG_RESTRICTED = 8; // 0x8 + field public static final int FLAG_SYSTEM = 2048; // 0x800 + field public static final int NO_PROFILE_GROUP_ID = -10000; // 0xffffd8f0 + field public boolean convertedFromPreCreated; + field public long creationTime; + field public int flags; + field public boolean guestToRemove; + field public String iconPath; + field public int id; + field public String lastLoggedInFingerprint; + field public long lastLoggedInTime; + field public String name; + field public boolean partial; + field public boolean preCreated; + field public int profileBadge; + field public int profileGroupId; + field public int restrictedProfileParentId; + field public int serialNumber; + field public String userType; + } + } package android.content.res { @@ -837,7 +922,7 @@ package android.graphics.drawable { package android.graphics.fonts { public class FontManager { - method @Nullable public android.text.FontConfig getFontConfig(); + method @NonNull public android.text.FontConfig getFontConfig(); 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); @@ -858,11 +943,21 @@ package android.graphics.fonts { package android.hardware { public final class SensorPrivacyManager { - method public boolean isIndividualSensorPrivacyEnabled(int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacy(int, boolean); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacyForProfileGroup(int, boolean); - field public static final int INDIVIDUAL_SENSOR_CAMERA = 2; // 0x2 - field public static final int INDIVIDUAL_SENSOR_MICROPHONE = 1; // 0x1 + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, boolean); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 } } @@ -1477,8 +1572,15 @@ package android.os { } public class UserManager { + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); + method public static boolean isGuestUserEphemeral(); method public static boolean isSplitSystemUser(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; } public final class VibrationAttributes implements android.os.Parcelable { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 87fb5b1b3b2e..a536efb2b488 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -468,7 +468,7 @@ GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double): GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float): GetterSetterNames: android.location.GnssMeasurement#setCodeType(String): - + GetterSetterNames: android.location.GnssMeasurement#setCorrelationVectors(java.util.Collection<android.location.CorrelationVector>): GetterSetterNames: android.location.GnssMeasurement#setFullInterSignalBiasNanos(double): @@ -480,7 +480,7 @@ GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasN GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasUncertaintyNanos(double): GetterSetterNames: android.location.GnssMeasurement#setSatellitePvt(android.location.SatellitePvt): - + GetterSetterNames: android.location.GnssMeasurement#setSnrInDb(double): GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): @@ -953,6 +953,32 @@ MissingNullability: android.content.pm.PackageManager#holdLock(android.os.IBinde MissingNullability: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context) parameter #0: +MissingNullability: android.content.pm.UserInfo#UserInfo(android.content.pm.UserInfo) parameter #0: + Missing nullability on parameter `orig` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #2: + Missing nullability on parameter `iconPath` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #2: + Missing nullability on parameter `iconPath` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #4: + Missing nullability on parameter `userType` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, int) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#getUserHandle(): + Missing nullability on method `getUserHandle` return +MissingNullability: android.content.pm.UserInfo#iconPath: + Missing nullability on field `iconPath` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#lastLoggedInFingerprint: + Missing nullability on field `lastLoggedInFingerprint` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#name: + Missing nullability on field `name` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#userType: + Missing nullability on field `userType` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#writeToParcel(android.os.Parcel, int) parameter #0: + Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.content.res.AssetManager#getOverlayablesToString(String) parameter #0: MissingNullability: android.content.res.Configuration#windowConfiguration: @@ -2433,6 +2459,38 @@ MutableBareField: android.content.AutofillOptions#disabledActivities: MutableBareField: android.content.AutofillOptions#whitelistedActivitiesForAugmentedAutofill: +MutableBareField: android.content.pm.UserInfo#convertedFromPreCreated: + Bare field convertedFromPreCreated must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#creationTime: + Bare field creationTime must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#flags: + Bare field flags must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#guestToRemove: + Bare field guestToRemove must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#iconPath: + Bare field iconPath must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#id: + Bare field id must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#lastLoggedInFingerprint: + Bare field lastLoggedInFingerprint must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#lastLoggedInTime: + Bare field lastLoggedInTime must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#name: + Bare field name must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#partial: + Bare field partial must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#preCreated: + Bare field preCreated must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#profileBadge: + Bare field profileBadge must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#profileGroupId: + Bare field profileGroupId must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#restrictedProfileParentId: + Bare field restrictedProfileParentId must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#serialNumber: + Bare field serialNumber must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#userType: + Bare field userType must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#cache: MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#dbName: @@ -2548,15 +2606,15 @@ NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED: NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE: NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE: @@ -2619,6 +2677,10 @@ NotCloseable: android.telephony.ims.stub.ImsUtImplBase: +NullableCollection: android.os.UserManager#createProfileForUser(String, String, int, int, String[]) parameter #4: + Type of parameter disallowedPackages in android.os.UserManager.createProfileForUser(String name, String userType, int flags, int userId, String[] disallowedPackages) is a nullable collection (`java.lang.String[]`); must be non-null + + OnNameExpected: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]): OnNameExpected: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): @@ -2729,6 +2791,8 @@ ParcelCreator: android.service.autofill.InternalValidator: ParcelNotFinal: android.app.WindowConfiguration: +ParcelNotFinal: android.content.pm.UserInfo: + Parcelable classes must be final: android.content.pm.UserInfo is not final ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event: ParcelNotFinal: android.os.IncidentManager.IncidentReport: diff --git a/core/java/android/app/ActivityManager.aidl b/core/java/android/app/ActivityManager.aidl index 341393c0d969..eb7cac076822 100644 --- a/core/java/android/app/ActivityManager.aidl +++ b/core/java/android/app/ActivityManager.aidl @@ -17,6 +17,7 @@ package android.app; parcelable ActivityManager.MemoryInfo; +parcelable ActivityManager.PendingIntentInfo; parcelable ActivityManager.ProcessErrorStateInfo; parcelable ActivityManager.RecentTaskInfo; parcelable ActivityManager.TaskDescription; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 220c332647a9..f905ec86aab7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -511,6 +511,7 @@ public class ActivityManager { /** @hide Process is hosting the current top activities. Note that this covers * all activities that are visible to the user. */ @UnsupportedAppUsage + @TestApi public static final int PROCESS_STATE_TOP = ProcessStateEnum.TOP; /** @hide Process is bound to a TOP app. */ @@ -518,6 +519,7 @@ public class ActivityManager { /** @hide Process is hosting a foreground service. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @TestApi public static final int PROCESS_STATE_FOREGROUND_SERVICE = ProcessStateEnum.FOREGROUND_SERVICE; /** @hide Process is hosting a foreground service due to a system binding. */ @@ -617,6 +619,7 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; /** @hide Process can access network despite any power saving resrictions */ + @TestApi public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3; /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @@ -3432,6 +3435,36 @@ public class ActivityManager { } /** + * Returns the process state of this uid. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public int getUidProcessState(int uid) { + try { + return getService().getUidProcessState(uid, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the process capability of this uid. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public @ProcessCapability int getUidProcessCapabilities(int uid) { + try { + return getService().getUidProcessCapabilities(uid, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the importance of a given package name, based on the processes that are * currently running. The return value is one of the importance constants defined * in {@link RunningAppProcessInfo}, giving you the highest importance of all the @@ -4776,4 +4809,71 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * A subset of immutable pending intent information suitable for caching on the client side. + * + * @hide + */ + public static final class PendingIntentInfo implements Parcelable { + + private final String mCreatorPackage; + private final int mCreatorUid; + private final boolean mImmutable; + private final int mIntentSenderType; + + public PendingIntentInfo(String creatorPackage, int creatorUid, boolean immutable, + int intentSenderType) { + mCreatorPackage = creatorPackage; + mCreatorUid = creatorUid; + mImmutable = immutable; + mIntentSenderType = intentSenderType; + } + + public String getCreatorPackage() { + return mCreatorPackage; + } + + public int getCreatorUid() { + return mCreatorUid; + } + + public boolean isImmutable() { + return mImmutable; + } + + public int getIntentSenderType() { + return mIntentSenderType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeString(mCreatorPackage); + parcel.writeInt(mCreatorUid); + parcel.writeBoolean(mImmutable); + parcel.writeInt(mIntentSenderType); + } + + public static final @NonNull Creator<PendingIntentInfo> CREATOR = + new Creator<PendingIntentInfo>() { + @Override + public PendingIntentInfo createFromParcel(Parcel in) { + return new PendingIntentInfo( + /* creatorPackage= */ in.readString(), + /* creatorUid= */ in.readInt(), + /* immutable= */ in.readBoolean(), + /* intentSenderType= */ in.readInt()); + } + + @Override + public PendingIntentInfo[] newArray(int size) { + return new PendingIntentInfo[size]; + } + }; + } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c31c22cca329..c812e8e1782a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -30,6 +30,7 @@ import android.content.pm.UserInfo; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.TransactionTooLargeException; import android.os.WorkSource; @@ -102,12 +103,15 @@ public abstract class ActivityManagerInternal { * Sets how long a {@link PendingIntent} can be temporarily allowlisted to bypass restrictions * such as Power Save mode. * @param target - * @param whitelistToken + * @param allowlistToken * @param duration temp allowlist duration in milliseconds. * @param type temp allowlist type defined at {@link TempAllowListType} + * @param reasonCode one of {@link ReasonCode} + * @param reason A human-readable reason for logging purposes. */ - public abstract void setPendingIntentWhitelistDuration(IIntentSender target, - IBinder whitelistToken, long duration, int type); + public abstract void setPendingIntentAllowlistDuration(IIntentSender target, + IBinder allowlistToken, long duration, @TempAllowListType int type, + @ReasonCode int reasonCode, @Nullable String reason); /** * Returns the flags set for a {@link PendingIntent}. @@ -127,20 +131,26 @@ public abstract class ActivityManagerInternal { IBinder allowlistToken); /** - * Allow DeviceIdleController to tell us about what apps are whitelisted. + * Allow DeviceIdleController to tell us about what apps are allowlisted. */ - public abstract void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids); + public abstract void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids); /** - * Update information about which app IDs are on the temp whitelist. + * Update information about which app IDs are on the temp allowlist. * @param appids the updated list of appIds in temp allowlist. * @param changingUid uid to add or remove to temp allowlist. * @param adding true to add to temp allowlist, false to remove from temp allowlist. * @param durationMs when adding is true, the duration to be in temp allowlist. * @param type temp allowlist type defined at {@link TempAllowListType}. + * @param reasonCode one of {@link ReasonCode} + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp allowlist, only valid when param adding + * is true. */ - public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, - boolean adding, long durationMs, @TempAllowListType int type); + public abstract void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, + boolean adding, long durationMs, @TempAllowListType int type, + @ReasonCode int reasonCode, + @Nullable String reason, int callingUid); /** * Get the procstate for the UID. The return value will be between @@ -335,10 +345,11 @@ public abstract class ActivityManagerInternal { * @param targetUid the UID that is been temp allowlisted. * @param duration temp allowlist duration in milliseconds. * @param type temp allowlist type defined at {@link TempAllowListType} - * @param tag + * @param reasonCode one of {@link ReasonCode} + * @param reason */ - public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag); + public abstract void tempAllowlistForPendingIntent(int callerPid, int callerUid, int targetUid, + long duration, int type, @ReasonCode int reasonCode, String reason); public abstract int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid, int realCallingUid, int realCallingPid, Intent intent, String resolvedType, @@ -495,9 +506,9 @@ public abstract class ActivityManagerInternal { /** * Sends a broadcast, assuming the caller to be the system and allowing the inclusion of an - * approved whitelist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the + * approved allowlist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the * broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are - * automatically whitelisted. + * automatically allowlisted. * * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature( * IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle, diff --git a/core/java/android/app/AnrController.java b/core/java/android/app/AnrController.java index cfc9d2715720..a0d4b3a6a753 100644 --- a/core/java/android/app/AnrController.java +++ b/core/java/android/app/AnrController.java @@ -23,7 +23,29 @@ package android.app; public interface AnrController { /** * Returns the delay in milliseconds for an ANR dialog that is about to be shown for - * {@code packageName}. + * {@code packageName} with {@code uid}. + * + * Implementations should only return a positive value if they actually expect the + * {@code packageName} to be delayed due to them. + + * If there are multiple controllers registered, the controller with the max delay will + * be selected and will receive an {@link #onAnrDelayStarted} callback at the start of the + * delay and an {@link #onAnrDelayCompleted} at the end of the delay. */ long getAnrDelayMillis(String packageName, int uid); + + /** + * Notifies the controller at the start of the ANR dialog delay for {@code packageName} with + * {@code uid}. The controller can decide to show a progress UI after this notification. + */ + void onAnrDelayStarted(String packageName, int uid); + + /** + * Notifies the controller at the end of the ANR dialog delay for {@code packageName} with + * {@code uid}. + * + * @return whether the ANR dialog should be shown or cancelled. {@code true} if the + * ANR dialog should be shown, {@code false} if it should be cancelled. + */ + boolean onAnrDelayCompleted(String packageName, int uid); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2f3b50b17d51..160844aacc46 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1141,23 +1141,20 @@ public class AppOpsManager { * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_MICROPHONE = 100; + public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE; /** * Phone call is using camera * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_CAMERA = 101; + public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA; /** * Audio is being recorded for hotword detection. * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_RECORD_AUDIO_HOTWORD = 102; + public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD; /** * Manage credentials in the system KeyChain. @@ -1184,10 +1181,29 @@ public class AppOpsManager { */ public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; + /** + * Fine location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 108; + public static final int _NUM_OP = 110; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1567,6 +1583,24 @@ public class AppOpsManager { */ public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm"; + /** + * Fine location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_FINE_LOCATION_SOURCE = "android:fine_location_source"; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1767,6 +1801,8 @@ public class AppOpsManager { OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM + OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE + OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE }; /** @@ -1881,6 +1917,8 @@ public class AppOpsManager { OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_RECORD_AUDIO_OUTPUT, OPSTR_SCHEDULE_EXACT_ALARM, + OPSTR_FINE_LOCATION_SOURCE, + OPSTR_COARSE_LOCATION_SOURCE, }; /** @@ -1996,6 +2034,8 @@ public class AppOpsManager { "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "RECORD_AUDIO_OUTPUT", "SCHEDULE_EXACT_ALARM", + "FINE_LOCATION_SOURCE", + "COARSE_LOCATION_SOURCE", }; /** @@ -2112,6 +2152,8 @@ public class AppOpsManager { Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, null, // no permission for OP_RECORD_AUDIO_OUTPUT Manifest.permission.SCHEDULE_EXACT_ALARM, + null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, + null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, }; /** @@ -2228,6 +2270,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2343,6 +2387,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2457,6 +2503,8 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM + AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE + AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2575,6 +2623,8 @@ public class AppOpsManager { true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER false, // RECORD_AUDIO_OUTPUT false, // SCHEDULE_EXACT_ALARM + false, // ACCESS_FINE_LOCATION_SOURCE + false, // ACCESS_COARSE_LOCATION_SOURCE }; /** diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 062cab457ebe..a6260d6d9cad 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1797,6 +1797,7 @@ public class ApplicationPackageManager extends PackageManager { } } + @UnsupportedAppUsage protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) { mContext = context; mPM = pm; diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 445fdd83f34a..2e06e9b80595 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,11 +16,13 @@ package android.app; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Build; import android.os.Bundle; import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; /** @@ -31,8 +33,11 @@ import android.os.PowerWhitelistManager.TempAllowListType; */ @SystemApi public class BroadcastOptions { - private long mTemporaryAppWhitelistDuration; - private @TempAllowListType int mTemporaryAppWhitelistType; + private long mTemporaryAppAllowlistDuration; + private @TempAllowListType int mTemporaryAppAllowlistType; + private @ReasonCode int mTemporaryAppAllowlistReasonCode = + PowerWhitelistManager.REASON_UNKNOWN; + private @Nullable String mTemporaryAppAllowlistReason; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; private boolean mDontSendToRestrictedApps = false; @@ -42,11 +47,17 @@ public class BroadcastOptions { * How long to temporarily put an app on the power allowlist when executing this broadcast * to it. */ - static final String KEY_TEMPORARY_APP_WHITELIST_DURATION - = "android:broadcast.temporaryAppWhitelistDuration"; + static final String KEY_TEMPORARY_APP_ALLOWLIST_DURATION + = "android:broadcast.temporaryAppAllowlistDuration"; - static final String KEY_TEMPORARY_APP_WHITELIST_TYPE - = "android:broadcast.temporaryAppWhitelistType"; + static final String KEY_TEMPORARY_APP_ALLOWLIST_TYPE + = "android:broadcast.temporaryAppAllowlistType"; + + static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE = + "android:broadcast.temporaryAppAllowlistReasonCode"; + + static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON = + "android:broadcast.temporaryAppAllowlistReason"; /** * Corresponds to {@link #setMinManifestReceiverApiLevel}. @@ -80,6 +91,7 @@ public class BroadcastOptions { @Deprecated public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + /** * @hide * @deprecated Use {@link android.os.PowerWhitelistManager# @@ -99,8 +111,11 @@ public class BroadcastOptions { /** @hide */ public BroadcastOptions(Bundle opts) { - mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); - mTemporaryAppWhitelistType = opts.getInt(KEY_TEMPORARY_APP_WHITELIST_TYPE); + mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION); + mTemporaryAppAllowlistType = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE); + mTemporaryAppAllowlistReasonCode = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, + PowerWhitelistManager.REASON_UNKNOWN); + mTemporaryAppAllowlistReason = opts.getString(KEY_TEMPORARY_APP_ALLOWLIST_REASON); mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, Build.VERSION_CODES.CUR_DEVELOPMENT); @@ -113,14 +128,16 @@ public class BroadcastOptions { * Set a duration for which the system should temporary place an application on the * power allowlist when this broadcast is being delivered to it. * @param duration The duration in milliseconds; 0 means to not place on allowlist. + * @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead. */ + @Deprecated @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long duration) { - mTemporaryAppWhitelistDuration = duration; - mTemporaryAppWhitelistType = - PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + setTemporaryAppAllowlist(duration, + PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + PowerWhitelistManager.REASON_UNKNOWN, null); } /** @@ -129,29 +146,69 @@ public class BroadcastOptions { * type. * @param type one of {@link TempAllowListType} * @param duration the duration in milliseconds; 0 means to not place on allowlist. + * @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead. */ + @Deprecated @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(@TempAllowListType int type, long duration) { - mTemporaryAppWhitelistDuration = duration; - mTemporaryAppWhitelistType = type; + setTemporaryAppAllowlist(duration, type, + PowerWhitelistManager.REASON_UNKNOWN, null); } /** - * Return {@link #setTemporaryAppWhitelistDuration}. + * Set a duration for which the system should temporary place an application on the + * power allowlist when this broadcast is being delivered to it, specify the temp allowlist + * type. + * @param duration the duration in milliseconds; 0 means to not place on allowlist. + * @param type one of {@link TempAllowListType} + * @param reasonCode one of {@link ReasonCode}, use + * {@link PowerWhitelistManager#REASON_UNKNOWN} if not sure. + * @param reason A human-readable reason explaining why the app is temp allowlisted. Only + * used for logging purposes. Could be null or empty string. + */ + @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) + public void setTemporaryAppAllowlist(long duration, @TempAllowListType int type, + @ReasonCode int reasonCode, @Nullable String reason) { + mTemporaryAppAllowlistDuration = duration; + mTemporaryAppAllowlistType = type; + mTemporaryAppAllowlistReasonCode = reasonCode; + mTemporaryAppAllowlistReason = reason; + } + + /** + * Return {@link #setTemporaryAppAllowlist}. + * @hide + */ + public long getTemporaryAppAllowlistDuration() { + return mTemporaryAppAllowlistDuration; + } + + /** + * Return {@link #mTemporaryAppAllowlistType}. * @hide */ - public long getTemporaryAppWhitelistDuration() { - return mTemporaryAppWhitelistDuration; + public @TempAllowListType int getTemporaryAppAllowlistType() { + return mTemporaryAppAllowlistType; } /** - * Return {@link #mTemporaryAppWhitelistType}. + * Return {@link #mTemporaryAppAllowlistReasonCode}. * @hide */ - public @TempAllowListType int getTemporaryAppWhitelistType() { - return mTemporaryAppWhitelistType; + public @ReasonCode int getTemporaryAppAllowlistReasonCode() { + return mTemporaryAppAllowlistReasonCode; + } + + /** + * Return {@link #mTemporaryAppAllowlistReason}. + * @hide + */ + public @Nullable String getTemporaryAppAllowlistReason() { + return mTemporaryAppAllowlistReason; } /** @@ -236,11 +293,17 @@ public class BroadcastOptions { */ public Bundle toBundle() { Bundle b = new Bundle(); - if (mTemporaryAppWhitelistDuration > 0) { - b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); + if (mTemporaryAppAllowlistDuration > 0) { + b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration); + } + if (mTemporaryAppAllowlistType != 0) { + b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType); + } + if (mTemporaryAppAllowlistReasonCode != PowerWhitelistManager.REASON_UNKNOWN) { + b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode); } - if (mTemporaryAppWhitelistType != 0) { - b.putInt(KEY_TEMPORARY_APP_WHITELIST_TYPE, mTemporaryAppWhitelistType); + if (mTemporaryAppAllowlistReason != null) { + b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason); } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 4ad13e1932dd..3a8172ea98b8 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -17,6 +17,7 @@ package android.app; import android.app.ActivityManager; +import android.app.ActivityManager.PendingIntentInfo; import android.app.ActivityTaskManager; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; @@ -248,7 +249,7 @@ interface IActivityManager { in IBinder token, in String resultWho, int requestCode, in Intent[] intents, in String[] resolvedTypes, int flags, in Bundle options, int userId); void cancelIntentSender(in IIntentSender sender); - String getPackageForIntentSender(in IIntentSender sender); + ActivityManager.PendingIntentInfo getInfoForIntentSender(in IIntentSender sender); void registerIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); void unregisterIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver); void enterSafeMode(); @@ -293,7 +294,6 @@ interface IActivityManager { int operationType); void backupAgentCreated(in String packageName, in IBinder agent, int userId); void unbindBackupAgent(in ApplicationInfo appInfo); - int getUidForIntentSender(in IIntentSender sender); int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, boolean requireFull, in String name, in String callerPackage); void addPackageDependency(in String packageName); @@ -345,7 +345,6 @@ interface IActivityManager { @UnsupportedAppUsage void unregisterProcessObserver(in IProcessObserver observer); boolean isIntentSenderTargetedToPackage(in IIntentSender sender); - boolean isIntentSenderImmutable(in IIntentSender sender); @UnsupportedAppUsage void updatePersistentConfiguration(in Configuration values); void updatePersistentConfigurationWithAttribution(in Configuration values, @@ -375,8 +374,6 @@ interface IActivityManager { void unstableProviderDied(in IBinder connection); @UnsupportedAppUsage boolean isIntentSenderAnActivity(in IIntentSender sender); - boolean isIntentSenderAForegroundService(in IIntentSender sender); - boolean isIntentSenderABroadcast(in IIntentSender sender); /** @deprecated Use {@link startActivityAsUserWithFeature} instead */ @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@code android.content.Context#createContextAsUser(android.os.UserHandle, int)} and {@link android.content.Context#startActivity(android.content.Intent)} instead") int startActivityAsUser(in IApplicationThread caller, in String callingPackage, @@ -552,7 +549,7 @@ interface IActivityManager { /** * Add a bare uid to the background restrictions whitelist. Only the system uid may call this. */ - void backgroundWhitelistUid(int uid); + void backgroundAllowlistUid(int uid); // Start of P transactions /** @@ -711,5 +708,5 @@ interface IActivityManager { /** Called by PendingIntent.queryIntentComponents() */ List<ResolveInfo> queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); - boolean isIntentSenderAService(in IIntentSender sender); + int getUidProcessCapabilities(int uid, in String callingPackage); } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index b1c39d39d414..7bb5d9a8d387 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -60,9 +60,9 @@ oneway interface ITaskStackListener { void onActivityForcedResizable(String packageName, int taskId, int reason); /** - * Called when we launched an activity that dismissed the docked stack. + * Called when we launched an activity that dismissed the docked task. */ - void onActivityDismissingDockedStack(); + void onActivityDismissingDockedTask(); /** * Called when an activity was requested to be launched on a secondary display but was not diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 899cdb5eb572..f7304fbee6d6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1371,6 +1371,18 @@ public class Notification implements Parcelable public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; /** + * {@link #extras} key: the color used as a hint for the Answer action button of a + * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. + */ + public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; + + /** + * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a + * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. + */ + public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; + + /** * {@link #extras} key: whether the notification should be colorized as * supplied to {@link Builder#setColorized(boolean)}. */ @@ -5444,6 +5456,11 @@ public class Notification implements Parcelable return p.allowColorization && mN.isColorized(); } + private boolean isCallActionColorCustomizable(StandardTemplateParams p) { + return isColorized(p) && mContext.getResources().getBoolean( + R.bool.config_callNotificationActionColorsRequireColorized); + } + private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { if (mN.mSmallIcon == null && mN.icon != 0) { mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); @@ -9066,6 +9083,8 @@ public class Notification implements Parcelable private PendingIntent mAnswerIntent; private PendingIntent mDeclineIntent; private PendingIntent mHangUpIntent; + private Integer mAnswerButtonColor; + private Integer mDeclineButtonColor; private Icon mVerificationIcon; private CharSequence mVerificationText; @@ -9176,6 +9195,28 @@ public class Notification implements Parcelable } /** + * Optional color to be used as a hint for the Answer action button's color. + * The system may change this color to ensure sufficient contrast with the background. + * The system may choose to disregard this hint if the notification is not colorized. + */ + @NonNull + public CallStyle setAnswerButtonColorHint(@ColorInt int color) { + mAnswerButtonColor = color; + return this; + } + + /** + * Optional color to be used as a hint for the Decline or Hang Up action button's color. + * The system may change this color to ensure sufficient contrast with the background. + * The system may choose to disregard this hint if the notification is not colorized. + */ + @NonNull + public CallStyle setDeclineButtonColorHint(@ColorInt int color) { + mDeclineButtonColor = color; + return this; + } + + /** * @hide */ public boolean displayCustomViewInline() { @@ -9234,40 +9275,47 @@ public class Notification implements Parcelable } @NonNull - private Action makeNegativeAction() { + private Action makeNegativeAction(@NonNull StandardTemplateParams p) { if (mDeclineIntent == null) { - return makeAction(R.drawable.ic_call_decline, + return makeAction(p, R.drawable.ic_call_decline, R.string.call_notification_hang_up_action, - R.color.call_notification_decline_color, mHangUpIntent); + mDeclineButtonColor, R.color.call_notification_decline_color, + mHangUpIntent); } else { - return makeAction(R.drawable.ic_call_decline, + return makeAction(p, R.drawable.ic_call_decline, R.string.call_notification_decline_action, - R.color.call_notification_decline_color, mDeclineIntent); + mDeclineButtonColor, R.color.call_notification_decline_color, + mDeclineIntent); } } @Nullable - private Action makeAnswerAction() { - return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer, + private Action makeAnswerAction(@NonNull StandardTemplateParams p) { + return mAnswerIntent == null ? null : makeAction(p, R.drawable.ic_call_answer, R.string.call_notification_answer_action, - R.color.call_notification_answer_color, mAnswerIntent); + mAnswerButtonColor, R.color.call_notification_answer_color, + mAnswerIntent); } @NonNull - private Action makeAction(@DrawableRes int icon, @StringRes int title, - @ColorRes int colorRes, PendingIntent intent) { + private Action makeAction(@NonNull StandardTemplateParams p, + @DrawableRes int icon, @StringRes int title, + @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { + if (colorInt == null || !mBuilder.isCallActionColorCustomizable(p)) { + colorInt = mBuilder.mContext.getColor(defaultColorRes); + } Action action = new Action.Builder(Icon.createWithResource("", icon), new SpannableStringBuilder().append(mBuilder.mContext.getString(title), - new ForegroundColorSpan(mBuilder.mContext.getColor(colorRes)), + new ForegroundColorSpan(colorInt), SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), intent).build(); action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); return action; } - private ArrayList<Action> makeActionsList() { - final Action negativeAction = makeNegativeAction(); - final Action answerAction = makeAnswerAction(); + private ArrayList<Action> makeActionsList(@NonNull StandardTemplateParams p) { + final Action negativeAction = makeNegativeAction(p); + final Action answerAction = makeAnswerAction(p); ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS); final Action lastAction; @@ -9356,7 +9404,7 @@ public class Notification implements Parcelable // Create the buttons for the generated actions list. int i = 0; - for (Action action : makeActionsList()) { + for (Action action : makeActionsList(p)) { final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p); if (i > 0) { // Clear start margin from non-first buttons to reduce the gap between buttons. @@ -9421,6 +9469,12 @@ public class Notification implements Parcelable if (mHangUpIntent != null) { extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); } + if (mAnswerButtonColor != null) { + extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); + } + if (mDeclineButtonColor != null) { + extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); + } fixTitleAndTextExtras(extras); } @@ -9447,6 +9501,10 @@ public class Notification implements Parcelable mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT); mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT); mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT); + mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) + ? extras.getInt(EXTRA_ANSWER_COLOR) : null; + mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) + ? extras.getInt(EXTRA_DECLINE_COLOR) : null; } /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 671315f37b20..549bd4b9fe6a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -16,6 +16,11 @@ package android.app; +import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; +import static android.app.ActivityManager.INTENT_SENDER_BROADCAST; +import static android.app.ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE; +import static android.app.ActivityManager.INTENT_SENDER_SERVICE; + import android.Manifest.permission; import android.annotation.IntDef; import android.annotation.NonNull; @@ -25,6 +30,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemApi.Client; import android.annotation.TestApi; +import android.app.ActivityManager.PendingIntentInfo; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -55,6 +61,7 @@ import com.android.internal.os.IResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; /** * A description of an Intent and target action to perform with it. Instances @@ -122,6 +129,9 @@ public final class PendingIntent implements Parcelable { private IBinder mWhitelistToken; private ArraySet<CancelListener> mCancelListeners; + // cached pending intent information + private @Nullable PendingIntentInfo mCachedInfo; + /** * It is now required to specify either {@link #FLAG_IMMUTABLE} * or {@link #FLAG_MUTABLE} when creating a PendingIntent. @@ -455,15 +465,14 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getActivityAsUser(Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options, UserHandle user) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, + INTENT_SENDER_ACTIVITY, packageName, context.getAttributionTag(), null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, options, user.getIdentifier()); @@ -596,7 +605,7 @@ public final class PendingIntent implements Parcelable { try { IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_ACTIVITY, packageName, + INTENT_SENDER_ACTIVITY, packageName, context.getAttributionTag(), null, null, requestCode, intents, resolvedTypes, flags, options, user.getIdentifier()); return target != null ? new PendingIntent(target) : null; @@ -630,7 +639,7 @@ public final class PendingIntent implements Parcelable { */ @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public static PendingIntent getBroadcast(Context context, int requestCode, - Intent intent, @Flags int flags) { + @NonNull Intent intent, @Flags int flags) { return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser()); } @@ -643,14 +652,13 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getBroadcastAsUser(Context context, int requestCode, Intent intent, int flags, UserHandle userHandle) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( - ActivityManager.INTENT_SENDER_BROADCAST, packageName, + INTENT_SENDER_BROADCAST, packageName, context.getAttributionTag(), null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, null, userHandle.getIdentifier()); @@ -687,7 +695,7 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getService(Context context, int requestCode, @NonNull Intent intent, @Flags int flags) { return buildServicePendingIntent(context, requestCode, intent, flags, - ActivityManager.INTENT_SENDER_SERVICE); + INTENT_SENDER_SERVICE); } /** @@ -717,14 +725,13 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getForegroundService(Context context, int requestCode, @NonNull Intent intent, @Flags int flags) { return buildServicePendingIntent(context, requestCode, intent, flags, - ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE); + INTENT_SENDER_FOREGROUND_SERVICE); } private static PendingIntent buildServicePendingIntent(Context context, int requestCode, Intent intent, int flags, int serviceKind) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); @@ -746,6 +753,7 @@ public final class PendingIntent implements Parcelable { * @return Returns a IntentSender object that wraps the sender of PendingIntent * */ + @NonNull public IntentSender getIntentSender() { return new IntentSender(mTarget, mWhitelistToken); } @@ -758,6 +766,7 @@ public final class PendingIntent implements Parcelable { try { ActivityManager.getService().cancelIntentSender(mTarget); } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -1000,13 +1009,9 @@ public final class PendingIntent implements Parcelable { * @deprecated Renamed to {@link #getCreatorPackage()}. */ @Deprecated + @NonNull public String getTargetPackage() { - try { - return ActivityManager.getService() - .getPackageForIntentSender(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCreatorPackage(); } /** @@ -1024,17 +1029,11 @@ public final class PendingIntent implements Parcelable { * only use this information to identify who you expect to be interacting with * through a {@link #send} call, not who gave you the PendingIntent.</p> * - * @return The package name of the PendingIntent, or null if there is - * none associated with it. + * @return The package name of the PendingIntent. */ - @Nullable + @NonNull public String getCreatorPackage() { - try { - return ActivityManager.getService() - .getPackageForIntentSender(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getCreatorPackage(); } /** @@ -1056,12 +1055,7 @@ public final class PendingIntent implements Parcelable { * none associated with it. */ public int getCreatorUid() { - try { - return ActivityManager.getService() - .getUidForIntentSender(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getCreatorUid(); } /** @@ -1149,18 +1143,12 @@ public final class PendingIntent implements Parcelable { * only use this information to identify who you expect to be interacting with * through a {@link #send} call, not who gave you the PendingIntent.</p> * - * @return The user handle of the PendingIntent, or null if there is - * none associated with it. + * @return The user handle of the PendingIntent */ - @Nullable + @NonNull public UserHandle getCreatorUserHandle() { - try { - int uid = ActivityManager.getService() - .getUidForIntentSender(mTarget); - return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int uid = getCachedInfo().getCreatorUid(); + return UserHandle.getUserHandleForUid(uid); } /** @@ -1180,12 +1168,7 @@ public final class PendingIntent implements Parcelable { * Check if this PendingIntent is marked with {@link #FLAG_IMMUTABLE}. */ public boolean isImmutable() { - try { - return ActivityManager.getService() - .isIntentSenderImmutable(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().isImmutable(); } /** @@ -1193,48 +1176,28 @@ public final class PendingIntent implements Parcelable { * {@link #getActivity} or {@link #getActivities}. */ public boolean isActivity() { - try { - return ActivityManager.getService() - .isIntentSenderAnActivity(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getIntentSenderType() == INTENT_SENDER_ACTIVITY; } /** * @return TRUE if this {@link PendingIntent} was created with {@link #getForegroundService}. */ public boolean isForegroundService() { - try { - return ActivityManager.getService() - .isIntentSenderAForegroundService(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getIntentSenderType() == INTENT_SENDER_FOREGROUND_SERVICE; } /** * @return TRUE if this {@link PendingIntent} was created with {@link #getService}. */ public boolean isService() { - try { - return ActivityManager.getService() - .isIntentSenderAService(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getIntentSenderType() == INTENT_SENDER_SERVICE; } /** * @return TRUE if this {@link PendingIntent} was created with {@link #getBroadcast}. */ public boolean isBroadcast() { - try { - return ActivityManager.getService() - .isIntentSenderABroadcast(mTarget); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getCachedInfo().getIntentSenderType() == INTENT_SENDER_BROADCAST; } /** @@ -1318,7 +1281,7 @@ public final class PendingIntent implements Parcelable { sb.append("PendingIntent{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(": "); - sb.append(mTarget != null ? mTarget.asBinder() : null); + sb.append(mTarget.asBinder()); sb.append('}'); return sb.toString(); } @@ -1326,9 +1289,7 @@ public final class PendingIntent implements Parcelable { /** @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - if (mTarget != null) { - proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); - } + proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); proto.end(token); } @@ -1345,8 +1306,7 @@ public final class PendingIntent implements Parcelable { } - public static final @android.annotation.NonNull Parcelable.Creator<PendingIntent> CREATOR - = new Parcelable.Creator<PendingIntent>() { + public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() { public PendingIntent createFromParcel(Parcel in) { IBinder target = in.readStrongBinder(); return target != null @@ -1400,11 +1360,11 @@ public final class PendingIntent implements Parcelable { * @hide */ public PendingIntent(IIntentSender target) { - mTarget = target; + mTarget = Objects.requireNonNull(target); } /*package*/ PendingIntent(IBinder target, Object cookie) { - mTarget = IIntentSender.Stub.asInterface(target); + mTarget = Objects.requireNonNull(IIntentSender.Stub.asInterface(target)); if (cookie != null) { mWhitelistToken = (IBinder)cookie; } @@ -1433,4 +1393,16 @@ public final class PendingIntent implements Parcelable { */ void onCancelled(PendingIntent intent); } + + private PendingIntentInfo getCachedInfo() { + if (mCachedInfo == null) { + try { + mCachedInfo = ActivityManager.getService().getInfoForIntentSender(mTarget); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + return mCachedInfo; + } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index dd5a9a8a28af..e16e40b6d572 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -47,6 +47,7 @@ import android.app.usage.IUsageStatsManager; import android.app.usage.NetworkStatsManager; import android.app.usage.StorageStatsManager; import android.app.usage.UsageStatsManager; +import android.apphibernation.AppHibernationManager; import android.appwidget.AppWidgetManager; import android.bluetooth.BluetoothManager; import android.companion.CompanionDeviceManager; @@ -1378,6 +1379,13 @@ public final class SystemServiceRegistry { IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE); return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b)); }}); + registerService(Context.APP_HIBERNATION_SERVICE, AppHibernationManager.class, + new CachedServiceFetcher<AppHibernationManager>() { + @Override + public AppHibernationManager createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(Context.APP_HIBERNATION_SERVICE); + return b == null ? null : new AppHibernationManager(ctx); + }}); registerService(Context.DREAM_SERVICE, DreamManager.class, new CachedServiceFetcher<DreamManager>() { @Override diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 1e382307a1a3..f523a7d29713 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -43,7 +43,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) + public void onActivityPinned(String packageName, int userId, int taskId, int rootTaskId) throws RemoteException { } @@ -66,7 +66,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void onActivityDismissingDockedStack() throws RemoteException { + public void onActivityDismissingDockedTask() throws RemoteException { } @Override diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index 2d203f57a66f..3abba43ae0a8 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -30,14 +30,17 @@ import android.util.Log; import android.util.Size; import com.android.internal.graphics.ColorUtils; +import com.android.internal.graphics.palette.CelebiQuantizer; import com.android.internal.graphics.palette.Palette; -import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import com.android.internal.util.ContrastColorUtil; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * Provides information about the colors of a wallpaper. @@ -94,16 +97,21 @@ public final class WallpaperColors implements Parcelable { private static final float DARK_PIXEL_CONTRAST = 6f; private static final float MAX_DARK_AREA = 0.025f; - private final ArrayList<Color> mMainColors; + private final List<Color> mMainColors; + private final Map<Integer, Integer> mAllColors; private int mColorHints; public WallpaperColors(Parcel parcel) { mMainColors = new ArrayList<>(); + mAllColors = new HashMap<>(); final int count = parcel.readInt(); for (int i = 0; i < count; i++) { final int colorInt = parcel.readInt(); Color color = Color.valueOf(colorInt); mMainColors.add(color); + + final int population = parcel.readInt(); + mAllColors.put(colorInt, population); } mColorHints = parcel.readInt(); } @@ -166,39 +174,22 @@ public final class WallpaperColors implements Parcelable { } final Palette palette = Palette - .from(bitmap) - .setQuantizer(new VariationalKMeansQuantizer()) - .maximumColorCount(5) - .clearFilters() + .from(bitmap, new CelebiQuantizer()) + .maximumColorCount(256) .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) .generate(); - // Remove insignificant colors and sort swatches by population final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches()); - final float minColorArea = bitmap.getWidth() * bitmap.getHeight() * MIN_COLOR_OCCURRENCE; - swatches.removeIf(s -> s.getPopulation() < minColorArea); swatches.sort((a, b) -> b.getPopulation() - a.getPopulation()); final int swatchesSize = swatches.size(); - Color primary = null, secondary = null, tertiary = null; - swatchLoop: + final Map<Integer, Integer> populationByColor = new HashMap<>(); for (int i = 0; i < swatchesSize; i++) { - Color color = Color.valueOf(swatches.get(i).getRgb()); - switch (i) { - case 0: - primary = color; - break; - case 1: - secondary = color; - break; - case 2: - tertiary = color; - break; - default: - // out of bounds - break swatchLoop; - } + Palette.Swatch swatch = swatches.get(i); + int colorInt = swatch.getInt(); + populationByColor.put(colorInt, swatch.getPopulation()); + } int hints = calculateDarkHints(bitmap); @@ -207,7 +198,7 @@ public final class WallpaperColors implements Parcelable { bitmap.recycle(); } - return new WallpaperColors(primary, secondary, tertiary, HINT_FROM_BITMAP | hints); + return new WallpaperColors(populationByColor, HINT_FROM_BITMAP | hints); } /** @@ -253,9 +244,13 @@ public final class WallpaperColors implements Parcelable { } mMainColors = new ArrayList<>(3); + mAllColors = new HashMap<>(); + mMainColors.add(primaryColor); + mAllColors.put(primaryColor.toArgb(), 0); if (secondaryColor != null) { mMainColors.add(secondaryColor); + mAllColors.put(secondaryColor.toArgb(), 0); } if (tertiaryColor != null) { if (secondaryColor == null) { @@ -263,8 +258,32 @@ public final class WallpaperColors implements Parcelable { + "secondaryColor is null"); } mMainColors.add(tertiaryColor); + mAllColors.put(tertiaryColor.toArgb(), 0); } + mColorHints = colorHints; + } + /** + * Constructs a new object from a set of colors, where hints can be specified. + * + * @param populationByColor Map with keys of colors, and value representing the number of + * occurrences of color in the wallpaper. + * @param colorHints A combination of WallpaperColor hints. + * @hide + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @see WallpaperColors#fromBitmap(Bitmap) + * @see WallpaperColors#fromDrawable(Drawable) + */ + public WallpaperColors(@NonNull Map<Integer, Integer> populationByColor, int colorHints) { + mAllColors = populationByColor; + + ArrayList<Map.Entry<Integer, Integer>> mapEntries = new ArrayList( + populationByColor.entrySet()); + mapEntries.sort((a, b) -> + a.getValue().compareTo(b.getValue()) + ); + mMainColors = mapEntries.stream().map(entry -> Color.valueOf(entry.getKey())).collect( + Collectors.toList()); mColorHints = colorHints; } @@ -293,6 +312,9 @@ public final class WallpaperColors implements Parcelable { for (int i = 0; i < count; i++) { Color color = mainColors.get(i); dest.writeInt(color.toArgb()); + Integer population = mAllColors.get(color.toArgb()); + int populationInt = (population != null) ? population : 0; + dest.writeInt(populationInt); } dest.writeInt(mColorHints); } @@ -336,6 +358,17 @@ public final class WallpaperColors implements Parcelable { return Collections.unmodifiableList(mMainColors); } + /** + * Map of all colors. Key is rgb integer, value is importance of color. + * + * @return List of colors. + * @hide + */ + public @NonNull Map<Integer, Integer> getAllColors() { + return Collections.unmodifiableMap(mAllColors); + } + + @Override public boolean equals(@Nullable Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 4ae1670e9041..d04ca1d9a48e 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -57,7 +57,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * TODO: Investigate combining with {@link #mAppBounds}. Can the latter be a product of the * former? */ - private Rect mBounds = new Rect(); + private final Rect mBounds = new Rect(); /** * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of @@ -71,7 +71,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * The maximum {@link Rect} bounds that an app can expect. It is used to report value of * {@link WindowManager#getMaximumWindowMetrics()}. */ - private Rect mMaxBounds = new Rect(); + private final Rect mMaxBounds = new Rect(); /** * The current rotation of this window container relative to the default @@ -240,9 +240,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mBounds, flags); - dest.writeParcelable(mAppBounds, flags); - dest.writeParcelable(mMaxBounds, flags); + mBounds.writeToParcel(dest, flags); + dest.writeTypedObject(mAppBounds, flags); + mMaxBounds.writeToParcel(dest, flags); dest.writeInt(mWindowingMode); dest.writeInt(mActivityType); dest.writeInt(mAlwaysOnTop); @@ -250,10 +250,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu dest.writeInt(mDisplayWindowingMode); } - private void readFromParcel(Parcel source) { - mBounds = source.readParcelable(Rect.class.getClassLoader()); - mAppBounds = source.readParcelable(Rect.class.getClassLoader()); - mMaxBounds = source.readParcelable(Rect.class.getClassLoader()); + /** @hide */ + public void readFromParcel(@NonNull Parcel source) { + mBounds.readFromParcel(source); + mAppBounds = source.readTypedObject(Rect.CREATOR); + mMaxBounds.readFromParcel(source); mWindowingMode = source.readInt(); mActivityType = source.readInt(); mAlwaysOnTop = source.readInt(); @@ -693,9 +694,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } protoOutputStream.write(WINDOWING_MODE, mWindowingMode); protoOutputStream.write(ACTIVITY_TYPE, mActivityType); - if (mBounds != null) { - mBounds.dumpDebug(protoOutputStream, BOUNDS); - } + mBounds.dumpDebug(protoOutputStream, BOUNDS); mMaxBounds.dumpDebug(protoOutputStream, MAX_BOUNDS); protoOutputStream.end(token); } @@ -719,11 +718,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu mAppBounds.readFromProto(proto, APP_BOUNDS); break; case (int) BOUNDS: - mBounds = new Rect(); mBounds.readFromProto(proto, BOUNDS); break; case (int) MAX_BOUNDS: - mMaxBounds = new Rect(); mMaxBounds.readFromProto(proto, MAX_BOUNDS); break; case (int) WINDOWING_MODE: diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 59e5144113c9..bb1ff6051d56 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2746,6 +2746,32 @@ public class DevicePolicyManager { @Retention(RetentionPolicy.SOURCE) public @interface PersonalAppsSuspensionReason {} + /** + * The default device owner type for a managed device. + * + * @hide + */ + public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; + + /** + * The device owner type for a financed device. + * + * @hide + */ + public static final int DEVICE_OWNER_TYPE_FINANCED = 1; + + /** + * Different device owner types for a managed device. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "DEVICE_OWNER_TYPE_" }, value = { + DEVICE_OWNER_TYPE_DEFAULT, + DEVICE_OWNER_TYPE_FINANCED + }) + public @interface DeviceOwnerType {} + /** @hide */ @TestApi public static final int OPERATION_LOCK_NOW = 1; @@ -7276,7 +7302,12 @@ public class DevicePolicyManager { /** * @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresPermission(allOf = { + android.Manifest.permission.MANAGE_DEVICE_ADMINS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL + }) public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing, int userHandle) { if (mService != null) { @@ -7453,8 +7484,10 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(ComponentName who, String ownerName, int userId) - throws IllegalArgumentException, IllegalStateException { + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean setDeviceOwner( + @NonNull ComponentName who, @Nullable String ownerName, @UserIdInt int userId) { if (mService != null) { try { return mService.setDeviceOwner(who, ownerName, userId); @@ -7521,7 +7554,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, + }) public ComponentName getDeviceOwnerComponentOnAnyUser() { return getDeviceOwnerComponentInner(/* callingUserOnly =*/ false); } @@ -10477,9 +10513,10 @@ public class DevicePolicyManager { /** * Reset record of previous system update freeze period the device went through. - * Only callable by ADB. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord() { throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); if (mService == null) { @@ -11207,9 +11244,11 @@ public class DevicePolicyManager { /** * Makes all accumulated network logs available to DPC in a new batch. - * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0. + * If throttled, returns time to wait in milliseconds, otherwise 0. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs() { if (mService == null) { return -1; @@ -11223,9 +11262,11 @@ public class DevicePolicyManager { /** * Forces a batch of security logs to be fetched from logd and makes it available for DPC. - * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0. + * If throttled, returns time to wait in milliseconds, otherwise 0. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs() { if (mService == null) { return 0; @@ -11657,7 +11698,10 @@ public class DevicePolicyManager { * @throws SecurityException if the caller is not shell / root or the admin package * isn't a test application see {@link ApplicationInfo#FLAG_TEST_APP}. */ - public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) { + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void forceRemoveActiveAdmin( + @NonNull ComponentName adminReceiver, @UserIdInt int userHandle) { try { mService.forceRemoveActiveAdmin(adminReceiver, userHandle); } catch (RemoteException re) { @@ -12727,8 +12771,11 @@ public class DevicePolicyManager { * * @hide */ - @RequiresPermission(value = android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, - conditional = true) + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }, conditional = true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull ComponentName who) { if (mService == null) { return; @@ -13460,6 +13507,57 @@ public class DevicePolicyManager { } /** + * Sets the device owner type for a managed device (e.g. financed device). + * + * @param admin The {@link DeviceAdminReceiver} that is the device owner. + * @param deviceOwnerType The device owner type is set to. Use + * {@link #DEVICE_OWNER_TYPE_DEFAULT} for the default device owner type. Use + * {@link #DEVICE_OWNER_TYPE_FINANCED} for the financed device owner type. + * + * @throws IllegalStateException When admin is not the device owner, or there is no device + * owner, or attempting to set the device owner type again for the same admin. + * @throws SecurityException If the caller does not have the permission + * {@link permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}. + * + * @hide + */ + public void setDeviceOwnerType(@NonNull ComponentName admin, + @DeviceOwnerType int deviceOwnerType) { + throwIfParentInstance("setDeviceOwnerType"); + if (mService != null) { + try { + mService.setDeviceOwnerType(admin, deviceOwnerType); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the device owner type for the admin used in + * {@link #setDeviceOwnerType(ComponentName, int)}. {@link #DEVICE_OWNER_TYPE_DEFAULT} + * would be returned when the device owner type is not set for the device owner admin. + * + * @param admin The {@link DeviceAdminReceiver} that is the device owner. + * + * @throws IllegalStateException When admin is not the device owner or there is no device owner. + * + * @hide + */ + @DeviceOwnerType + public int getDeviceOwnerType(@NonNull ComponentName admin) { + throwIfParentInstance("getDeviceOwnerType"); + if (mService != null) { + try { + return mService.getDeviceOwnerType(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return DEVICE_OWNER_TYPE_DEFAULT; + } + + /** * 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. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 8a87b16b760b..ac1592d2d2a1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -503,6 +503,9 @@ interface IDevicePolicyManager { UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage); void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage); + void setDeviceOwnerType(in ComponentName admin, in int deviceOwnerType); + int getDeviceOwnerType(in ComponentName admin); + void resetDefaultCrossProfileIntentFilters(int userId); boolean canAdminGrantSensorsPermissionsForUser(int userId); diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 673de8fa7c8c..dae565e12fd7 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -361,36 +361,7 @@ public class BackupManager { try { // All packages, current transport IRestoreSession binder = - sService.beginRestoreSessionForUser(mContext.getUserId(), null, null, - OperationType.BACKUP); - if (binder != null) { - session = new RestoreSession(mContext, binder); - } - } catch (RemoteException e) { - Log.e(TAG, "beginRestoreSession() couldn't connect"); - } - } - return session; - } - - /** - * Begin the process of restoring data from backup. See the - * {@link android.app.backup.RestoreSession} class for documentation on that process. - * - * @param operationType Type of the operation, see {@link OperationType} - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.BACKUP) - public RestoreSession beginRestoreSession(@OperationType int operationType) { - RestoreSession session = null; - checkServiceBinder(); - if (sService != null) { - try { - // All packages, current transport - IRestoreSession binder = - sService.beginRestoreSessionForUser(mContext.getUserId(), null, null, - operationType); + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null); if (binder != null) { session = new RestoreSession(mContext, binder); } @@ -801,7 +772,7 @@ public class BackupManager { @SystemApi @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[] packages, BackupObserver observer) { - return requestBackup(packages, observer, null, 0, OperationType.BACKUP); + return requestBackup(packages, observer, null, 0); } /** @@ -826,31 +797,6 @@ public class BackupManager { @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[] packages, BackupObserver observer, BackupManagerMonitor monitor, int flags) { - return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP); - } - - /** - * Request an immediate backup, providing an observer to which results of the backup operation - * will be published. The Android backup system will decide for each package whether it will - * be full app data backup or key/value-pair-based backup. - * - * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all - * provided packages using the remote transport. - * - * @param packages List of package names to backup. - * @param observer The {@link BackupObserver} to receive callbacks during the backup - * operation. Could be {@code null}. - * @param monitor The {@link BackupManagerMonitorWrapper} to receive callbacks of important - * events during the backup operation. Could be {@code null}. - * @param flags {@link #FLAG_NON_INCREMENTAL_BACKUP}. - * @param operationType {@link OperationType} - * @return {@link BackupManager#SUCCESS} on success; nonzero on error. - * @throws IllegalArgumentException on null or empty {@code packages} param. - * @hide - */ - @RequiresPermission(android.Manifest.permission.BACKUP) - public int requestBackup(String[] packages, BackupObserver observer, - BackupManagerMonitor monitor, int flags, @OperationType int operationType) { checkServiceBinder(); if (sService != null) { try { @@ -860,8 +806,7 @@ public class BackupManager { BackupManagerMonitorWrapper monitorWrapper = monitor == null ? null : new BackupManagerMonitorWrapper(monitor); - return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags, - operationType); + return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags); } catch (RemoteException e) { Log.e(TAG, "requestBackup() couldn't connect"); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index f7ed6f1f2feb..3701ea825933 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -19,8 +19,10 @@ package android.app.backup; import static android.app.backup.BackupManager.OperationType; import android.annotation.Nullable; +import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.os.ParcelFileDescriptor; @@ -33,6 +35,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -93,6 +96,15 @@ public class FullBackup { public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = "fakeClientSideEncryption"; + @StringDef({ + ConfigSection.CLOUD_BACKUP, + ConfigSection.DEVICE_TRANSFER + }) + private @interface ConfigSection { + String CLOUD_BACKUP = "cloud-backup"; + String DEVICE_TRANSFER = "device-transfer"; + } + /** * Identify {@link BackupScheme} object by package and operation type * (see {@link OperationType}) it corresponds to. @@ -273,6 +285,7 @@ public class FullBackup { private final static String TAG_INCLUDE = "include"; private final static String TAG_EXCLUDE = "exclude"; + final int mDataExtractionRules; final int mFullBackupContent; @OperationType final int mOperationType; final PackageManager mPackageManager; @@ -394,7 +407,10 @@ public class FullBackup { ArraySet<PathWithRequiredFlags> mExcludes; BackupScheme(Context context, @OperationType int operationType) { - mFullBackupContent = context.getApplicationInfo().fullBackupContent; + ApplicationInfo applicationInfo = context.getApplicationInfo(); + + mDataExtractionRules = applicationInfo.dataExtractionRulesRes; + mFullBackupContent = applicationInfo.fullBackupContent; mOperationType = operationType; mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); mPackageManager = context.getPackageManager(); @@ -468,34 +484,98 @@ public class FullBackup { mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>(); mExcludes = new ArraySet<PathWithRequiredFlags>(); - if (mFullBackupContent == 0) { - // android:fullBackupContent="true" which means that we'll do everything. + if (mFullBackupContent == 0 && mDataExtractionRules == 0) { + // No scheme specified via either new or legacy config, will copy everything. if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\""); } } else { - // android:fullBackupContent="@xml/some_resource". + // Scheme is present. if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { - Log.v(FullBackup.TAG_XML_PARSER, - "android:fullBackupContent - found xml resource"); + Log.v(FullBackup.TAG_XML_PARSER, "Found xml scheme: " + + "android:fullBackupContent=" + mFullBackupContent + + "; android:dataExtractionRules=" + mDataExtractionRules); } - XmlResourceParser parser = null; + try { - parser = mPackageManager - .getResourcesForApplication(mPackageName) - .getXml(mFullBackupContent); - parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes); + parseSchemeForOperationType(mOperationType); } catch (PackageManager.NameNotFoundException e) { // Throw it as an IOException throw new IOException(e); - } finally { - if (parser != null) { - parser.close(); - } } } } + private void parseSchemeForOperationType(@OperationType int operationType) + throws PackageManager.NameNotFoundException, IOException, XmlPullParserException { + String configSection = getConfigSectionForOperationType(operationType); + if (configSection == null) { + Slog.w(TAG, "Given operation type isn't supported by backup scheme: " + + operationType); + return; + } + + if (mDataExtractionRules != 0) { + // New config is present. Use it if it has configuration for this operation + // type. + try (XmlResourceParser parser = getParserForResource(mDataExtractionRules)) { + parseNewBackupSchemeFromXmlLocked(parser, configSection, mExcludes, mIncludes); + } + if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) { + // Found configuration in the new config, we will use it. + return; + } + } + + // TODO(b/180523564): Ignore the old config for apps targeting Android S+ during D2D. + + if (mFullBackupContent != 0) { + // Fall back to the old config. + try (XmlResourceParser parser = getParserForResource(mFullBackupContent)) { + parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes); + } + } + } + + @Nullable + private String getConfigSectionForOperationType(@OperationType int operationType) { + switch (operationType) { + case OperationType.BACKUP: + return ConfigSection.CLOUD_BACKUP; + case OperationType.MIGRATION: + return ConfigSection.DEVICE_TRANSFER; + default: + return null; + } + } + + private XmlResourceParser getParserForResource(int resourceId) + throws PackageManager.NameNotFoundException { + return mPackageManager + .getResourcesForApplication(mPackageName) + .getXml(resourceId); + } + + private void parseNewBackupSchemeFromXmlLocked(XmlPullParser parser, + @ConfigSection String configSection, + Set<PathWithRequiredFlags> excludes, + Map<String, Set<PathWithRequiredFlags>> includes) + throws IOException, XmlPullParserException { + verifyTopLevelTag(parser, "data-extraction-rules"); + + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event != XmlPullParser.START_TAG || !configSection.equals(parser.getName())) { + continue; + } + + // TODO(b/180523028): Parse required attributes for rules (e.g. encryption). + parseRules(parser, excludes, includes, Optional.of(0), configSection); + } + + logParsingResults(excludes, includes); + } + @VisibleForTesting public void parseBackupSchemeFromXmlLocked(XmlPullParser parser, Set<PathWithRequiredFlags> excludes, @@ -503,7 +583,7 @@ public class FullBackup { throws IOException, XmlPullParserException { verifyTopLevelTag(parser, "full-backup-content"); - parseRules(parser, excludes, includes, Optional.empty()); + parseRules(parser, excludes, includes, Optional.empty(), "full-backup-content"); logParsingResults(excludes, includes); } @@ -532,10 +612,12 @@ public class FullBackup { private void parseRules(XmlPullParser parser, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes, - Optional<Integer> maybeRequiredFlags) + Optional<Integer> maybeRequiredFlags, + String endingTag) throws IOException, XmlPullParserException { int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT + && !parser.getName().equals(endingTag)) { switch (event) { case XmlPullParser.START_TAG: validateInnerTagContents(parser); diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index e1bbc08e72f3..bf5be95c4ab0 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -547,11 +547,9 @@ interface IBackupManager { * set can be restored. * @param transportID The name of the transport to use for the restore operation. * May be null, in which case the current active transport is used. - * @param operationType Type of the operation, see {@link BackupManager#OperationType} * @return An interface to the restore session, or null on error. */ - IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID, - int operationType); + IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID); /** * Notify the backup manager that a BackupAgent has completed the operation @@ -680,7 +678,7 @@ interface IBackupManager { * {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id. */ int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, - int flags, int operationType); + int flags); /** * Cancel all running backups. After this call returns, no currently running backups will diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java index 132af4b2f67b..dd2ba7db03ae 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/core/java/android/app/people/PeopleSpaceTile.java @@ -29,6 +29,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,7 @@ public class PeopleSpaceTile implements Parcelable { private String mId; private CharSequence mUserName; private Icon mUserIcon; - private int mUid; + private UserHandle mUserHandle; private Uri mContactUri; private String mPackageName; private String mBirthdayText; @@ -64,7 +65,7 @@ public class PeopleSpaceTile implements Parcelable { mUserName = b.mUserName; mUserIcon = b.mUserIcon; mContactUri = b.mContactUri; - mUid = b.mUid; + mUserHandle = b.mUserHandle; mPackageName = b.mPackageName; mBirthdayText = b.mBirthdayText; mLastInteractionTimestamp = b.mLastInteractionTimestamp; @@ -95,8 +96,8 @@ public class PeopleSpaceTile implements Parcelable { return mContactUri; } - public int getUid() { - return mUid; + public UserHandle getUserHandle() { + return mUserHandle; } public String getPackageName() { @@ -165,7 +166,7 @@ public class PeopleSpaceTile implements Parcelable { Builder builder = new Builder(mId, mUserName.toString(), mUserIcon, mIntent); builder.setContactUri(mContactUri); - builder.setUid(mUid); + builder.setUserHandle(mUserHandle); builder.setPackageName(mPackageName); builder.setBirthdayText(mBirthdayText); builder.setLastInteractionTimestamp(mLastInteractionTimestamp); @@ -186,7 +187,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mUserName; private Icon mUserIcon; private Uri mContactUri; - private int mUid; + private UserHandle mUserHandle; private String mPackageName; private String mBirthdayText; private long mLastInteractionTimestamp; @@ -212,7 +213,7 @@ public class PeopleSpaceTile implements Parcelable { mId = info.getId(); mUserName = info.getLabel(); mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0)); - mUid = info.getUserId(); + mUserHandle = info.getUserHandle(); mPackageName = info.getPackage(); mContactUri = getContactUri(info); } @@ -222,7 +223,7 @@ public class PeopleSpaceTile implements Parcelable { mId = info.getId(); mUserName = info.getLabel(); mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0)); - mUid = info.getUserId(); + mUserHandle = info.getUserHandle(); mPackageName = info.getPackage(); mContactUri = getContactUri(info); mStatuses = channel.getStatuses(); @@ -265,9 +266,9 @@ public class PeopleSpaceTile implements Parcelable { return this; } - /** Sets the associated uid. */ - public Builder setUid(int uid) { - mUid = uid; + /** Sets the associated {@code userHandle}. */ + public Builder setUserHandle(UserHandle userHandle) { + mUserHandle = userHandle; return this; } @@ -349,7 +350,7 @@ public class PeopleSpaceTile implements Parcelable { mUserName = in.readCharSequence(); mUserIcon = in.readParcelable(Icon.class.getClassLoader()); mContactUri = in.readParcelable(Uri.class.getClassLoader()); - mUid = in.readInt(); + mUserHandle = in.readParcelable(UserHandle.class.getClassLoader()); mPackageName = in.readString(); mBirthdayText = in.readString(); mLastInteractionTimestamp = in.readLong(); @@ -375,7 +376,7 @@ public class PeopleSpaceTile implements Parcelable { dest.writeCharSequence(mUserName); dest.writeParcelable(mUserIcon, flags); dest.writeParcelable(mContactUri, flags); - dest.writeInt(mUid); + dest.writeParcelable(mUserHandle, flags); dest.writeString(mPackageName); dest.writeString(mBirthdayText); dest.writeLong(mLastInteractionTimestamp); diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java index 71a800f2085e..066aadae1476 100644 --- a/core/java/android/app/time/LocationTimeZoneManager.java +++ b/core/java/android/app/time/LocationTimeZoneManager.java @@ -37,7 +37,7 @@ public final class LocationTimeZoneManager { /** * The name of the service for shell commands */ - public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; + public static final String SERVICE_NAME = "location_time_zone_manager"; /** * A shell command that starts the service (after stop). diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 4277292e19a3..fc54c716d4ec 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -40,6 +40,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -92,7 +93,10 @@ public class AppWidgetHostView extends FrameLayout { int mLayoutId = -1; private InteractionHandler mInteractionHandler; private boolean mOnLightBackground; - PointF mCurrentSize = null; + private PointF mCurrentSize = null; + private RemoteViews.ColorResources mColorResources = null; + // Stores the last remote views last inflated. + private RemoteViews mLastInflatedRemoteViews = null; private Executor mAsyncExecutor; private CancellationSignal mLastExecutionSignal; @@ -358,7 +362,7 @@ public class AppWidgetHostView extends FrameLayout { PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips); if (!newSize.equals(mCurrentSize)) { mCurrentSize = newSize; - mLayoutId = -1; // Prevents recycling the view. + reapplyLastRemoteViews(); } } @@ -368,7 +372,7 @@ public class AppWidgetHostView extends FrameLayout { public void clearCurrentSize() { if (mCurrentSize != null) { mCurrentSize = null; - mLayoutId = -1; + reapplyLastRemoteViews(); } } @@ -477,10 +481,18 @@ public class AppWidgetHostView extends FrameLayout { * AppWidget provider. Will animate into these new views as needed */ public void updateAppWidget(RemoteViews remoteViews) { + this.mLastInflatedRemoteViews = remoteViews; applyRemoteViews(remoteViews, true); } /** + * Reapply the last inflated remote views, or the default view is none was inflated. + */ + private void reapplyLastRemoteViews() { + applyRemoteViews(mLastInflatedRemoteViews, true); + } + + /** * @hide */ protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) { @@ -518,7 +530,8 @@ public class AppWidgetHostView extends FrameLayout { // layout matches, try recycling it if (content == null && layoutId == mLayoutId) { try { - remoteViews.reapply(mContext, mView, mInteractionHandler); + remoteViews.reapply(mContext, mView, mInteractionHandler, mCurrentSize, + mColorResources); content = mView; recycled = true; if (LOGD) Log.d(TAG, "was able to recycle existing layout"); @@ -530,7 +543,8 @@ public class AppWidgetHostView extends FrameLayout { // Try normal RemoteView inflation if (content == null) { try { - content = remoteViews.apply(mContext, this, mInteractionHandler, mCurrentSize); + content = remoteViews.apply(mContext, this, mInteractionHandler, + mCurrentSize, mColorResources); if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { exception = e; @@ -583,7 +597,8 @@ public class AppWidgetHostView extends FrameLayout { mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, true), mInteractionHandler, - mCurrentSize); + mCurrentSize, + mColorResources); } catch (Exception e) { // Reapply failed. Try apply } @@ -594,7 +609,8 @@ public class AppWidgetHostView extends FrameLayout { mAsyncExecutor, new ViewApplyListener(remoteViews, layoutId, false), mInteractionHandler, - mCurrentSize); + mCurrentSize, + mColorResources); } } @@ -662,9 +678,13 @@ public class AppWidgetHostView extends FrameLayout { protected Context getRemoteContext() { try { // Return if cloned successfully, otherwise default - return mContext.createApplicationContext( + Context newContext = mContext.createApplicationContext( mInfo.providerInfo.applicationInfo, Context.CONTEXT_RESTRICTED); + if (mColorResources != null) { + mColorResources.apply(newContext); + } + return newContext; } catch (NameNotFoundException e) { Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); return mContext; @@ -819,4 +839,37 @@ public class AppWidgetHostView extends FrameLayout { } }; } + + /** + * Set the dynamically overloaded color resources. + * + * {@code colorMapping} maps a predefined set of color resources to their ARGB + * representation. Any entry not in the predefined set of colors will be ignored. + * + * Calling this method will trigger a full re-inflation of the App Widget. + * + * The color resources that can be overloaded are the ones whose name is prefixed with + * {@code system_primary_}, {@code system_secondary_} or {@code system_neutral_}, for example + * {@link android.R.color#system_primary_500}. + */ + public void setColorResources(@NonNull SparseIntArray colorMapping) { + mColorResources = RemoteViews.ColorResources.create(mContext, colorMapping); + mLayoutId = -1; + reapplyLastRemoteViews(); + } + + /** + * Reset the dynamically overloaded resources, reverting to the default values for + * all the colors. + * + * If colors were defined before, calling this method will trigger a full re-inflation of the + * App Widget. + */ + public void resetColorResources() { + if (mColorResources != null) { + mColorResources = null; + mLayoutId = -1; + reapplyLastRemoteViews(); + } + } } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index aac8710e8691..38919f61d9df 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -23,6 +23,8 @@ import android.annotation.RequiresFeature; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.IServiceConnection; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; @@ -1095,7 +1097,9 @@ public class AppWidgetManager { * * @hide */ - public void setBindAppWidgetPermission(String packageName, int userId, boolean permission) { + @TestApi + public void setBindAppWidgetPermission( + @NonNull String packageName, @UserIdInt int userId, boolean permission) { if (mService == null) { return; } diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index d893a5e49aa9..6ac1c1ae61ec 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -332,12 +332,13 @@ public class AppWidgetProviderInfo implements Parcelable { /** * Resource id for the description of the AppWidget. + * * <p>This field corresponds to the <code>android:description</code> attribute in the AppWidget * meta-data file. */ @SuppressLint("MutableBareField") @IdRes - public int descriptionResource; + public int descriptionRes; /** * Flags indicating various features supported by the widget. These are hints to the widget @@ -385,7 +386,7 @@ public class AppWidgetProviderInfo implements Parcelable { this.widgetCategory = in.readInt(); this.providerInfo = in.readTypedObject(ActivityInfo.CREATOR); this.widgetFeatures = in.readInt(); - this.descriptionResource = in.readInt(); + this.descriptionRes = in.readInt(); } /** @@ -442,14 +443,22 @@ public class AppWidgetProviderInfo implements Parcelable { return loadDrawable(context, density, previewImage, false); } - /** Loads localized description for the app widget. */ + /** + * Loads localized description for the app widget. + * + * <p>Description is intended to be displayed in the UI of the widget picker. + * + * @param context Context for accessing resources. + * + * @return CharSequence for app widget description for the current locale. + */ @Nullable - public final String loadDescription(@NonNull Context context) { - if (ResourceId.isValid(descriptionResource)) { + public final CharSequence loadDescription(@NonNull Context context) { + if (ResourceId.isValid(descriptionRes)) { return context.getPackageManager() .getText( providerInfo.packageName, - descriptionResource, + descriptionRes, providerInfo.applicationInfo) .toString() .trim(); @@ -499,7 +508,7 @@ public class AppWidgetProviderInfo implements Parcelable { out.writeInt(this.widgetCategory); out.writeTypedObject(this.providerInfo, flags); out.writeInt(this.widgetFeatures); - out.writeInt(this.descriptionResource); + out.writeInt(this.descriptionRes); } @Override @@ -528,7 +537,7 @@ public class AppWidgetProviderInfo implements Parcelable { that.widgetCategory = this.widgetCategory; that.providerInfo = this.providerInfo; that.widgetFeatures = this.widgetFeatures; - that.descriptionResource = this.descriptionResource; + that.descriptionRes = this.descriptionRes; return that; } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index cd91aa9b16b7..53aaae0470e2 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -943,12 +943,13 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setBufferMillis(@BluetoothCodecConfig.SourceCodecType int codec, int value) { - if (VDBG) log("setBufferMillis(" + codec + ", " + value + ")"); + public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec, + int value) { + if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.setBufferMillis(codec, value); + return service.setBufferLengthMillis(codec, value); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index ec46da0dcf0e..8d4157259ff7 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -3654,12 +3654,12 @@ public final class BluetoothAdapter { } @Override - public void onDeviceDisconnected(BluetoothDevice device) { + public void onDeviceDisconnected(BluetoothDevice device, int hciReason) { for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: mBluetoothConnectionCallbackExecutorMap.entrySet()) { BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); Executor executor = callbackExecutorEntry.getValue(); - executor.execute(() -> callback.onDeviceDisconnected(device)); + executor.execute(() -> callback.onDeviceDisconnected(device, hciReason)); } } }; @@ -3764,8 +3764,155 @@ public final class BluetoothAdapter { /** * Callback triggered when a bluetooth device (classic or BLE) is disconnected * @param device is the disconnected bluetooth device + * @param reason is the disconnect reason */ - public void onDeviceDisconnected(BluetoothDevice device) {} + public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {} + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "REASON_" }, value = { + REASON_UNKNOWN, + REASON_LOCAL_REQUEST, + REASON_REMOTE_REQUEST, + REASON_LOCAL_ERROR, + REASON_REMOTE_ERROR, + REASON_TIMEOUT, + REASON_SECURITY, + REASON_SYSTEM_POLICY, + REASON_RESOURCE_LIMIT_REACHED, + REASON_CONNECTION_EXISTS, + REASON_BAD_PARAMETERS}) + public @interface DisconnectReason {} + + /** + * Indicates that the ACL disconnected due to an unknown reason. + */ + public static final int REASON_UNKNOWN = 0; + + /** + * Indicates that the ACL disconnected due to an explicit request from the local device. + * <p> + * Example cause: This is a normal disconnect reason, e.g., user/app initiates + * disconnection. + */ + public static final int REASON_LOCAL_REQUEST = 1; + + /** + * Indicates that the ACL disconnected due to an explicit request from the remote device. + * <p> + * Example cause: This is a normal disconnect reason, e.g., user/app initiates + * disconnection. + * <p> + * Example solution: The app can also prompt the user to check their remote device. + */ + public static final int REASON_REMOTE_REQUEST = 2; + + /** + * Generic disconnect reason indicating the ACL disconnected due to an error on the local + * device. + * <p> + * Example solution: Prompt the user to check their local device (e.g., phone, car + * headunit). + */ + public static final int REASON_LOCAL_ERROR = 3; + + /** + * Generic disconnect reason indicating the ACL disconnected due to an error on the remote + * device. + * <p> + * Example solution: Prompt the user to check their remote device (e.g., headset, car + * headunit, watch). + */ + public static final int REASON_REMOTE_ERROR = 4; + + /** + * Indicates that the ACL disconnected due to a timeout. + * <p> + * Example cause: remote device might be out of range. + * <p> + * Example solution: Prompt user to verify their remote device is on or in + * connection/pairing mode. + */ + public static final int REASON_TIMEOUT = 5; + + /** + * Indicates that the ACL disconnected due to link key issues. + * <p> + * Example cause: Devices are either unpaired or remote device is refusing our pairing + * request. + * <p> + * Example solution: Prompt user to unpair and pair again. + */ + public static final int REASON_SECURITY = 6; + + /** + * Indicates that the ACL disconnected due to the local device's system policy. + * <p> + * Example cause: privacy policy, power management policy, permissions, etc. + * <p> + * Example solution: Prompt the user to check settings, or check with their system + * administrator (e.g. some corp-managed devices do not allow OPP connection). + */ + public static final int REASON_SYSTEM_POLICY = 7; + + /** + * Indicates that the ACL disconnected due to resource constraints, either on the local + * device or the remote device. + * <p> + * Example cause: controller is busy, memory limit reached, maximum number of connections + * reached. + * <p> + * Example solution: The app should wait and try again. If still failing, prompt the user + * to disconnect some devices, or toggle Bluetooth on the local and/or the remote device. + */ + public static final int REASON_RESOURCE_LIMIT_REACHED = 8; + + /** + * Indicates that the ACL disconnected because another ACL connection already exists. + */ + public static final int REASON_CONNECTION_EXISTS = 9; + + /** + * Indicates that the ACL disconnected due to incorrect parameters passed in from the app. + * <p> + * Example solution: Change parameters and try again. If error persists, the app can report + * telemetry and/or log the error in a bugreport. + */ + public static final int REASON_BAD_PARAMETERS = 10; + + /** + * Returns human-readable strings corresponding to {@link DisconnectReason}. + */ + public static String disconnectReasonText(@DisconnectReason int reason) { + switch (reason) { + case REASON_UNKNOWN: + return "Reason unknown"; + case REASON_LOCAL_REQUEST: + return "Local request"; + case REASON_REMOTE_REQUEST: + return "Remote request"; + case REASON_LOCAL_ERROR: + return "Local error"; + case REASON_REMOTE_ERROR: + return "Remote error"; + case REASON_TIMEOUT: + return "Timeout"; + case REASON_SECURITY: + return "Security"; + case REASON_SYSTEM_POLICY: + return "System policy"; + case REASON_RESOURCE_LIMIT_REACHED: + return "Resource constrained"; + case REASON_CONNECTION_EXISTS: + return "Connection already exists"; + case REASON_BAD_PARAMETERS: + return "Bad parameters"; + default: + return "Unrecognized disconnect reason: " + reason; + } + } } /** diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index ff6cffb272a5..0312a2190a4b 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -18,7 +18,9 @@ package android.bluetooth; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -30,6 +32,7 @@ import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -37,44 +40,60 @@ import java.util.List; * * @hide */ +@SystemApi public final class BluetoothMapClient implements BluetoothProfile { private static final String TAG = "BluetoothMapClient"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); + /** @hide */ public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; + /** @hide */ public static final String ACTION_MESSAGE_RECEIVED = "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; /* Actions to be used for pending intents */ + /** @hide */ public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; + /** @hide */ public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; /** * Action to notify read status changed + * + * @hide */ public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; /** * Action to notify deleted status changed + * + * @hide */ public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; - /* Extras used in ACTION_MESSAGE_RECEIVED intent. - * NOTE: HANDLE is only valid for a single session with the device. */ + /** + * Extras used in ACTION_MESSAGE_RECEIVED intent. + * NOTE: HANDLE is only valid for a single session with the device. + */ + /** @hide */ public static final String EXTRA_MESSAGE_HANDLE = "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE"; + /** @hide */ public static final String EXTRA_MESSAGE_TIMESTAMP = "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP"; + /** @hide */ public static final String EXTRA_MESSAGE_READ_STATUS = "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS"; + /** @hide */ public static final String EXTRA_SENDER_CONTACT_URI = "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI"; + /** @hide */ public static final String EXTRA_SENDER_CONTACT_NAME = "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; @@ -84,6 +103,8 @@ public final class BluetoothMapClient implements BluetoothProfile { * Possible values are: * true: deleted * false: undeleted + * + * @hide */ public static final String EXTRA_MESSAGE_DELETED_STATUS = "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; @@ -93,24 +114,42 @@ public final class BluetoothMapClient implements BluetoothProfile { * Possible values are: * 0: failure * 1: success + * + * @hide */ public static final String EXTRA_RESULT_CODE = "android.bluetooth.device.extra.RESULT_CODE"; - /** There was an error trying to obtain the state */ + /** + * There was an error trying to obtain the state + * @hide + */ public static final int STATE_ERROR = -1; + /** @hide */ public static final int RESULT_FAILURE = 0; + /** @hide */ public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ + /** + * Connection canceled before completion. + * @hide + */ public static final int RESULT_CANCELED = 2; - + /** @hide */ private static final int UPLOADING_FEATURE_BITMASK = 0x08; - /** Parameters in setMessageStatus */ + /* + * UNREAD, READ, UNDELETED, DELETED are passed as parameters + * to setMessageStatus to indicate the messages new state. + */ + + /** @hide */ public static final int UNREAD = 0; + /** @hide */ public static final int READ = 1; + /** @hide */ public static final int UNDELETED = 2; + /** @hide */ public static final int DELETED = 3; private BluetoothAdapter mAdapter; @@ -132,19 +171,12 @@ public final class BluetoothMapClient implements BluetoothProfile { mProfileConnector.connect(context, listener); } - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - /** * Close the connection to the backing service. * Other public functions of BluetoothMap will return default error * results once close() has been called. Multiple invocations of close() * are ok. + * @hide */ public void close() { mProfileConnector.disconnect(); @@ -158,6 +190,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * Returns true if the specified Bluetooth device is connected. * Returns false if not connected, or if this proxy object is not * currently connected to the Map service. + * @hide */ public boolean isConnected(BluetoothDevice device) { if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); @@ -225,6 +258,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * Get the list of connected devices. Currently at most one. * * @return list of connected devices + * @hide */ @Override public List<BluetoothDevice> getConnectedDevices() { @@ -246,6 +280,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * Get the list of devices matching specified states. Currently at most one. * * @return list of matching devices + * @hide */ @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { @@ -267,6 +302,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * Get connection state of device * * @return device connection state + * @hide */ @Override public int getConnectionState(BluetoothDevice device) { @@ -383,11 +419,44 @@ public final class BluetoothMapClient implements BluetoothProfile { * Send an SMS message to either the contacts primary number or the telephone number specified. * * @param device Bluetooth device + * @param contacts Uri Collection of the contacts + * @param message Message to be sent + * @param sentIntent intent issued when message is sent + * @param deliveredIntent intent issued when message is delivered + * @return true if the message is enqueued, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SEND_SMS) + public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts, + @NonNull String message, @Nullable PendingIntent sentIntent, + @Nullable PendingIntent deliveredIntent) { + if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); + final IBluetoothMapClient service = getService(); + if (service != null && isEnabled() && isValidDevice(device)) { + try { + return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]), + message, sentIntent, deliveredIntent); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + return false; + } + + /** + * Send a message. + * + * Send an SMS message to either the contacts primary number or the telephone number specified. + * + * @param device Bluetooth device * @param contacts Uri[] of the contacts * @param message Message to be sent * @param sentIntent intent issued when message is sent * @param deliveredIntent intent issued when message is delivered * @return true if the message is enqueued, false on error + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, @@ -410,6 +479,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @param device Bluetooth device * @return true if the message is enqueued, false on error + * @hide */ public boolean getUnreadMessages(BluetoothDevice device) { if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); @@ -431,6 +501,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * @param device The Bluetooth device to get this value for. * @return Returns true if the Uploading bit value in SDP record's * MapSupportedFeatures field is set. False is returned otherwise. + * @hide */ public boolean isUploadingSupported(BluetoothDevice device) { final IBluetoothMapClient service = getService(); @@ -457,7 +528,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for * "deleted", otherwise return error * @return <code>true</code> if request has been sent, <code>false</code> on error - * + * @hide */ @RequiresPermission(Manifest.permission.READ_SMS) public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index c31b04e81456..201d6c495d98 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -186,6 +186,7 @@ public interface BluetoothProfile { * * @hide */ + @SystemApi int MAP_CLIENT = 18; /** diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java index 7e5ec1e78435..97d97232b7a6 100644 --- a/core/java/android/bluetooth/BufferConstraints.java +++ b/core/java/android/bluetooth/BufferConstraints.java @@ -90,7 +90,7 @@ public final class BufferConstraints implements Parcelable { * @hide */ @SystemApi - public @Nullable BufferConstraint getCodec(@BluetoothCodecConfig.SourceCodecType int codec) { + public @Nullable BufferConstraint forCodec(@BluetoothCodecConfig.SourceCodecType int codec) { return mBufferConstraints.get(codec); } } diff --git a/core/java/android/companion/DeviceNotAssociatedException.java b/core/java/android/companion/DeviceNotAssociatedException.java index bebb6c9ff7eb..f8a7a7cdeffe 100644 --- a/core/java/android/companion/DeviceNotAssociatedException.java +++ b/core/java/android/companion/DeviceNotAssociatedException.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; * An exception for a case when a given device was not * {@link CompanionDeviceManager#associate associated} to the calling app. */ -public class DeviceNotAssociatedException extends Exception { +public class DeviceNotAssociatedException extends RuntimeException { /** @hide */ public DeviceNotAssociatedException(@Nullable String deviceName) { super("Device not associated with the current app: " + deviceName); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 46d8900e59a1..230c985d1dc8 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -132,8 +132,11 @@ public abstract class ContentResolver implements ContentInterface { public static final String SYNC_EXTRAS_ACCOUNT = "account"; /** - * If this extra is set to true, the sync request will be scheduled - * at the front of the sync request queue and without any delay + * If this extra is set to true, the sync request will be scheduled at the front of the + * sync request queue, but it is still subject to JobScheduler quota and throttling due to + * App Standby buckets. + * + * <p>This is different from {@link #SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB}. */ public static final String SYNC_EXTRAS_EXPEDITED = "expedited"; @@ -145,6 +148,29 @@ public abstract class ContentResolver implements ContentInterface { public static final String SYNC_EXTRAS_REQUIRE_CHARGING = "require_charging"; /** + * Run this sync operation as an "expedited job" + * (see {@link android.app.job.JobInfo.Builder#setExpedited(boolean)}). + * Normally (if this flag isn't specified), sync operations are executed as regular + * {@link android.app.job.JobService} jobs. + * + * <p> Because Expedited Jobs have various restrictions compared to regular jobs, this flag + * cannot be combined with certain other flags, otherwise an + * <code>IllegalArgumentException</code> will be thrown. Notably, because Expedited Jobs do not + * support various constraints, the following restriction apply: + * <ul> + * <li>Can't be used with {@link #SYNC_EXTRAS_REQUIRE_CHARGING} + * <li>Can't be used with {@link #SYNC_EXTRAS_EXPEDITED} + * <li>Can't be used on periodic syncs. + * <li>When an expedited-job-sync fails and a retry is scheduled, the retried sync will be + * scheduled as a regular job unless {@link #SYNC_EXTRAS_IGNORE_BACKOFF} is set. + * </ul> + * + * <p>This is different from {@link #SYNC_EXTRAS_EXPEDITED}. + */ + @SuppressLint("IntentName") + public static final String SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB = "schedule_as_expedited_job"; + + /** * @deprecated instead use * {@link #SYNC_EXTRAS_MANUAL} */ @@ -3220,6 +3246,18 @@ public abstract class ContentResolver implements ContentInterface { } /** + * {@hide} + * Helper function to throw an <code>IllegalArgumentException</code> if any illegal + * extras were set for a sync scheduled as an expedited job. + * + * @param extras bundle to validate. + */ + public static boolean hasInvalidScheduleAsEjExtras(Bundle extras) { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED); + } + + /** * Specifies that a sync should be requested with the specified the account, authority, * and extras at the given frequency. If there is already another periodic sync scheduled * with the account, authority and extras then a new periodic sync won't be added, instead @@ -3233,7 +3271,8 @@ public abstract class ContentResolver implements ContentInterface { * Periodic syncs are not allowed to have any of {@link #SYNC_EXTRAS_DO_NOT_RETRY}, * {@link #SYNC_EXTRAS_IGNORE_BACKOFF}, {@link #SYNC_EXTRAS_IGNORE_SETTINGS}, * {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE}, - * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true. + * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL}, + * {@link #SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB} set to true. * If any are supplied then an {@link IllegalArgumentException} will be thrown. * * <p>This method requires the caller to hold the permission @@ -3273,16 +3312,14 @@ public abstract class ContentResolver implements ContentInterface { * @param extras bundle to validate. */ public static boolean invalidPeriodicExtras(Bundle extras) { - if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false) || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false) - || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { - return true; - } - return false; + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) + || extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false); } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e20f706c2c35..f3a4e1f79955 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -370,6 +370,25 @@ public abstract class Context { /*********** Hidden flags below this line ***********/ /** + * Flag for {@link #bindService}: allow the process hosting the target service to be treated + * as if it's as important as a perceptible app to the user and avoid the oom killer killing + * this process in low memory situations until there aren't any other processes left but the + * ones which are user-perceptible. + * + * @hide + */ + public static final int BIND_ALMOST_PERCEPTIBLE = 0x000010000; + + /** + * Flag for {@link #bindService}: allow the process hosting the target service to gain + * {@link ActivityManager#PROCESS_CAPABILITY_NETWORK}, which allows it be able + * to access network regardless of any power saving restrictions. + * + * @hide + */ + public static final int BIND_ALLOW_NETWORK_ACCESS = 0x00020000; + + /** * Flag for {@link #bindService}: allow background foreground service starts from the bound * service's process. * This flag is only respected if the caller is holding @@ -6674,15 +6693,6 @@ public abstract class Context { } /** - * Indicates if this context is a visual context such as {@link android.app.Activity} or - * a context created from {@link #createWindowContext(int, Bundle)}. - * @hide - */ - public boolean isUiContext() { - throw new RuntimeException("Not implemented. Must override in a subclass."); - } - - /** * Returns {@code true} if the context is a UI context which can access UI components such as * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI @@ -6694,12 +6704,16 @@ public abstract class Context { * {@link #createWindowContext(int, Bundle)} or * {@link android.inputmethodservice.InputMethodService InputMethodService} * </p> + * <p> + * Note that even if it is allowed programmatically, it is not suggested to override this + * method to bypass {@link android.os.strictmode.IncorrectContextUseViolation} verification. + * </p> * * @see #getDisplay() * @see #getSystemService(String) * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse() */ - public static boolean isUiContext(@NonNull Context context) { - return context.isUiContext(); + public boolean isUiContext() { + throw new RuntimeException("Not implemented. Must override in a subclass."); } } diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index 858d1e498783..b1252fd0b21f 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -18,6 +18,7 @@ package android.content; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManager.PendingIntentInfo; import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.Handler; @@ -60,6 +61,9 @@ public class IntentSender implements Parcelable { private final IIntentSender mTarget; IBinder mWhitelistToken; + // cached pending intent information + private @Nullable PendingIntentInfo mCachedInfo; + /** * Exception thrown when trying to send through a PendingIntent that * has been canceled or is otherwise no longer able to execute the request. @@ -209,13 +213,7 @@ public class IntentSender implements Parcelable { */ @Deprecated public String getTargetPackage() { - try { - return ActivityManager.getService() - .getPackageForIntentSender(mTarget); - } catch (RemoteException e) { - // Should never happen. - return null; - } + return getCreatorPackage(); } /** @@ -228,13 +226,7 @@ public class IntentSender implements Parcelable { * none associated with it. */ public String getCreatorPackage() { - try { - return ActivityManager.getService() - .getPackageForIntentSender(mTarget); - } catch (RemoteException e) { - // Should never happen. - return null; - } + return getCachedInfo().getCreatorPackage(); } /** @@ -247,13 +239,7 @@ public class IntentSender implements Parcelable { * none associated with it. */ public int getCreatorUid() { - try { - return ActivityManager.getService() - .getUidForIntentSender(mTarget); - } catch (RemoteException e) { - // Should never happen. - return -1; - } + return getCachedInfo().getCreatorUid(); } /** @@ -268,14 +254,8 @@ public class IntentSender implements Parcelable { * none associated with it. */ public UserHandle getCreatorUserHandle() { - try { - int uid = ActivityManager.getService() - .getUidForIntentSender(mTarget); - return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; - } catch (RemoteException e) { - // Should never happen. - return null; - } + int uid = getCachedInfo().getCreatorUid(); + return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; } /** @@ -384,4 +364,16 @@ public class IntentSender implements Parcelable { public IntentSender(IBinder target) { mTarget = IIntentSender.Stub.asInterface(target); } + + private PendingIntentInfo getCachedInfo() { + if (mCachedInfo == null) { + try { + mCachedInfo = ActivityManager.getService().getInfoForIntentSender(mTarget); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + return mCachedInfo; + } } diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java index 9e568a40e0ee..e1e6f75d152f 100644 --- a/core/java/android/content/SyncRequest.java +++ b/core/java/android/content/SyncRequest.java @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; @@ -58,6 +59,8 @@ public class SyncRequest implements Parcelable { private final boolean mIsAuthority; /** Sync should be run in lieu of other syncs. */ private final boolean mIsExpedited; + /** Sync sound be ran as an expedited job. */ + private final boolean mIsScheduledAsExpeditedJob; /** * {@hide} @@ -79,6 +82,14 @@ public class SyncRequest implements Parcelable { /** * {@hide} + * @return whether this sync is scheduled as an expedited job. + */ + public boolean isScheduledAsExpeditedJob() { + return mIsScheduledAsExpeditedJob; + } + + /** + * {@hide} * * @return account object for this sync. * @throws IllegalArgumentException if this function is called for a request that targets a @@ -149,6 +160,7 @@ public class SyncRequest implements Parcelable { parcel.writeInt((mDisallowMetered ? 1 : 0)); parcel.writeInt((mIsAuthority ? 1 : 0)); parcel.writeInt((mIsExpedited? 1 : 0)); + parcel.writeInt(mIsScheduledAsExpeditedJob ? 1 : 0); parcel.writeParcelable(mAccountToSync, flags); parcel.writeString(mAuthority); } @@ -161,6 +173,7 @@ public class SyncRequest implements Parcelable { mDisallowMetered = (in.readInt() != 0); mIsAuthority = (in.readInt() != 0); mIsExpedited = (in.readInt() != 0); + mIsScheduledAsExpeditedJob = (in.readInt() != 0); mAccountToSync = in.readParcelable(null); mAuthority = in.readString(); } @@ -174,6 +187,7 @@ public class SyncRequest implements Parcelable { mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC); mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER); mIsExpedited = b.mExpedited; + mIsScheduledAsExpeditedJob = b.mScheduleAsExpeditedJob; mExtras = new Bundle(b.mCustomExtras); // For now we merge the sync config extras & the custom extras into one bundle. // TODO: pass the configuration extras through separately. @@ -258,6 +272,11 @@ public class SyncRequest implements Parcelable { */ private boolean mRequiresCharging; + /** + * Whether the sync should be scheduled as an expedited job. + */ + private boolean mScheduleAsExpeditedJob; + public Builder() { } @@ -309,7 +328,8 @@ public class SyncRequest implements Parcelable { * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE}, * {@link ContentResolver#SYNC_EXTRAS_FORCE}, * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}, - * {@link ContentResolver#SYNC_EXTRAS_MANUAL} + * {@link ContentResolver#SYNC_EXTRAS_MANUAL}, + * {@link ContentResolver#SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB} * set to true. If any are supplied then an <code>IllegalArgumentException</code> will * be thrown. * @@ -500,6 +520,22 @@ public class SyncRequest implements Parcelable { } /** + * Convenience function for setting + * {@link ContentResolver#SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB}. + * + * <p> Not to be confused with {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}. + * + * <p> Not valid for periodic syncs, expedited syncs, and syncs that require charging - an + * <code>IllegalArgumentException</code> will be thrown in {@link #build()}. + * + * @param scheduleAsExpeditedJob whether to schedule as an expedited job. Default false. + */ + public @NonNull Builder setScheduleAsExpeditedJob(boolean scheduleAsExpeditedJob) { + mScheduleAsExpeditedJob = scheduleAsExpeditedJob; + return this; + } + + /** * Performs validation over the request and throws the runtime exception * <code>IllegalArgumentException</code> if this validation fails. * @@ -507,11 +543,6 @@ public class SyncRequest implements Parcelable { * builder. */ public SyncRequest build() { - // Validate the extras bundle - ContentResolver.validateSyncExtrasBundle(mCustomExtras); - if (mCustomExtras == null) { - mCustomExtras = new Bundle(); - } // Combine builder extra flags into the config bundle. mSyncConfigExtras = new Bundle(); if (mIgnoreBackoff) { @@ -532,17 +563,35 @@ public class SyncRequest implements Parcelable { if (mExpedited) { mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); } + if (mScheduleAsExpeditedJob) { + mSyncConfigExtras.putBoolean( + ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, true); + } if (mIsManual) { mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); } + + if (mCustomExtras == null) { + mCustomExtras = new Bundle(); + } + // Validate the extras bundles + ContentResolver.validateSyncExtrasBundle(mCustomExtras); + // If this is a periodic sync ensure than invalid extras were not set. if (mSyncType == SYNC_TYPE_PERIODIC) { - // If this is a periodic sync ensure than invalid extras were not set. if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) { throw new IllegalArgumentException("Illegal extras were set"); } } + // If this sync is scheduled as an EJ, ensure that invalid extras were not set. + if (mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB) + || mScheduleAsExpeditedJob) { + if (ContentResolver.hasInvalidScheduleAsEjExtras(mCustomExtras) + || ContentResolver.hasInvalidScheduleAsEjExtras(mSyncConfigExtras)) { + throw new IllegalArgumentException("Illegal extras were set"); + } + } // Ensure that a target for the sync has been set. if (mSyncTarget == SYNC_TARGET_UNKNOWN) { throw new IllegalArgumentException("Must specify an adapter with" + diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index dec2c3d7fe48..0aa1be94d279 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -136,6 +136,18 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int fullBackupContent = 0; /** + * Applications can set this attribute to an xml resource within their app where they specified + * the rules determining which files and directories can be copied from the device as part of + * backup or transfer operations. + *<p> + * Set from the {@link android.R.styleable#AndroidManifestApplication_dataExtractionRules} + * attribute in the manifest. + * + * @hide + */ + public int dataExtractionRulesRes = 0; + + /** * <code>true</code> if the package is capable of presenting a unified interface representing * multiple profiles. * @hide @@ -1520,6 +1532,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "fullBackupContent=" + (fullBackupContent < 0 ? "false" : "true")); } + if (dataExtractionRulesRes != 0) { + pw.println(prefix + "dataExtractionRules=@xml/" + dataExtractionRulesRes); + } pw.println(prefix + "crossProfile=" + (crossProfile ? "true" : "false")); if (networkSecurityConfigRes != 0) { pw.println(prefix + "networkSecurityConfigRes=0x" @@ -1749,6 +1764,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; fullBackupContent = orig.fullBackupContent; + dataExtractionRulesRes = orig.dataExtractionRulesRes; crossProfile = orig.crossProfile; networkSecurityConfigRes = orig.networkSecurityConfigRes; category = orig.category; @@ -1836,6 +1852,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(descriptionRes); dest.writeInt(uiOptions); dest.writeInt(fullBackupContent); + dest.writeInt(dataExtractionRulesRes); dest.writeBoolean(crossProfile); dest.writeInt(networkSecurityConfigRes); dest.writeInt(category); @@ -1920,6 +1937,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { descriptionRes = source.readInt(); uiOptions = source.readInt(); fullBackupContent = source.readInt(); + dataExtractionRulesRes = source.readInt(); crossProfile = source.readBoolean(); networkSecurityConfigRes = source.readInt(); category = source.readInt(); diff --git a/core/java/android/content/pm/DataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl index d40012fd5718..29d472e6e927 100644 --- a/core/java/android/content/pm/DataLoaderParamsParcel.aidl +++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl @@ -23,7 +23,7 @@ import android.content.pm.DataLoaderType; * @hide */ parcelable DataLoaderParamsParcel { - DataLoaderType type; + DataLoaderType type = DataLoaderType.NONE; @utf8InCpp String packageName; @utf8InCpp String className; @utf8InCpp String arguments; diff --git a/core/java/android/content/pm/InstallationFileParcel.aidl b/core/java/android/content/pm/InstallationFileParcel.aidl index b7efc1947cc3..09d1a3291b69 100644 --- a/core/java/android/content/pm/InstallationFileParcel.aidl +++ b/core/java/android/content/pm/InstallationFileParcel.aidl @@ -24,7 +24,7 @@ import android.content.pm.InstallationFileLocation; */ parcelable InstallationFileParcel { String name; - InstallationFileLocation location; + InstallationFileLocation location = InstallationFileLocation.UNKNOWN; long size; byte[] metadata; byte[] signature; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 567501cba70b..42cbe3586db3 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1173,7 +1173,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public @Nullable DataLoaderParams getDataLoaderParams() { try { DataLoaderParamsParcel data = mSession.getDataLoaderParams(); @@ -1213,7 +1212,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes, @NonNull byte[] metadata, @Nullable byte[] signature) { try { @@ -1237,7 +1235,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void removeFile(@FileLocation int location, @NonNull String name) { try { mSession.removeFile(location, name); @@ -2050,9 +2047,7 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(allOf = { - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.USE_INSTALLER_V2}) + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) { this.dataLoaderParams = dataLoaderParams; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f9122b1f6c8b..29dea6bb09db 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3601,7 +3601,7 @@ public abstract class PackageManager { * 1 - IncFs v1, core features, no PerUid support. Optional in R. * 2 - IncFs v2, PerUid support, fs-verity support. Required in S. * - * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2() + * @see IncrementalManager#getVersion() * @hide */ @SystemApi @@ -3623,11 +3623,26 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for - * camera. When sensory privacy for the camera is enabled no camera data is send to clients, + * microphone. When sensory privacy for the microphone is enabled no microphone data is sent to + * clients, e.g. all audio data is silent. + * + * @hide + */ + @SystemApi + @TestApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for + * camera. When sensory privacy for the camera is enabled no camera data is sent to clients, * e.g. the view finder in a camera app would appear blank. * * @hide */ + @SystemApi + @TestApi @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; @@ -4060,16 +4075,6 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17; /** - * Permission flag: The permission is restricted but the app is exempt - * from the restriction and is allowed to hold this permission in its - * full form and the exemption is provided by the held roles. - * - * @hide - */ - @SystemApi - public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18; - - /** * Permission flag: This location permission is selected as the level of granularity of * location accuracy. * Example: If this flag is set for ACCESS_FINE_LOCATION, FINE location is the selected location @@ -4098,8 +4103,7 @@ public abstract class PackageManager { public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT - | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT - | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; /** * Mask for all permission flags. @@ -4184,20 +4188,11 @@ public abstract class PackageManager { */ public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2; - /** - * Permission allowlist flag: permissions exempted by the system - * when being granted a role. - * Permissions can also be exempted by the installer, the system, or on - * upgrade. - */ - public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3; - /** @hide */ @IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = { FLAG_PERMISSION_WHITELIST_SYSTEM, FLAG_PERMISSION_WHITELIST_INSTALLER, - FLAG_PERMISSION_WHITELIST_UPGRADE, - FLAG_PERMISSION_ALLOWLIST_ROLE + FLAG_PERMISSION_WHITELIST_UPGRADE }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionWhitelistFlags {} @@ -5229,10 +5224,6 @@ public abstract class PackageManager { * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag. * Can be accessed by pre-installed holders of a dedicated permission or the * installer on record. - * - * <li>one for cases where the system exempts the permission when granting a role. - * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can - * be accessed by pre-installed holders of a dedicated permission. * </ol> * * <p> @@ -5251,7 +5242,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to access a whitelist that you have no access to. */ @@ -5291,10 +5281,6 @@ public abstract class PackageManager { * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag. * Can be modified by pre-installed holders of a dedicated permission or the installer * on record. - * - * <li>one for cases where the system exempts the permission when permission when - * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} - * flag. Can be modified by pre-installed holders of a dedicated permission. * </ol> * * <p>You need to specify the whitelists for which to set the whitelisted permissions @@ -5318,7 +5304,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ @@ -5388,7 +5373,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 0e70a3e4e600..83baca668d55 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -411,14 +411,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4; /** - * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code> - * value of {@link android.R.attr#permissionFlags}. - * - * <p> Modifier for permission restriction. This permission cannot be exempted by the installer. - */ - public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5; - - /** * Flag for {@link #flags}, indicating that this permission has been * installed into the system's globally defined permissions. */ @@ -724,11 +716,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { } /** @hide */ - public boolean isInstallerExemptIgnored() { - return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; - } - - /** @hide */ public boolean isAppOp() { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; } diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index d81dff8f2908..cfb6e1b572aa 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; @@ -47,6 +48,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@TestApi public class UserInfo implements Parcelable { /** diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 7a01392a24e8..29edd405be6b 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -260,6 +260,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setFullBackupContent(int fullBackupContent); + ParsingPackage setDataExtractionRules(int dataExtractionRules); + ParsingPackage setHasDomainUrls(boolean hasDomainUrls); ParsingPackage setIconRes(int iconRes); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index c1a93d8c2428..067787d725d9 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -334,6 +334,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private int descriptionRes; private int fullBackupContent; + private int dataExtractionRules; private int iconRes; private int installLocation = ParsingPackageUtils.PARSE_DEFAULT_INSTALL_LOCATION; private int labelRes; @@ -1015,6 +1016,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { appInfo.enabled = getBoolean(Booleans.ENABLED); // appInfo.enabledSetting appInfo.fullBackupContent = fullBackupContent; + appInfo.dataExtractionRulesRes = dataExtractionRules; // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy // appInfo.mHiddenApiPolicy // appInfo.hiddenUntilInstalled @@ -1163,6 +1165,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { dest.writeInt(this.compatibleWidthLimitDp); dest.writeInt(this.descriptionRes); dest.writeInt(this.fullBackupContent); + dest.writeInt(this.dataExtractionRules); dest.writeInt(this.iconRes); dest.writeInt(this.installLocation); dest.writeInt(this.labelRes); @@ -1284,6 +1287,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.compatibleWidthLimitDp = in.readInt(); this.descriptionRes = in.readInt(); this.fullBackupContent = in.readInt(); + this.dataExtractionRules = in.readInt(); this.iconRes = in.readInt(); this.installLocation = in.readInt(); this.labelRes = in.readInt(); @@ -1808,6 +1812,11 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public int getDataExtractionRules() { + return dataExtractionRules; + } + + @Override public int getIconRes() { return iconRes; } @@ -2264,6 +2273,12 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public ParsingPackageImpl setDataExtractionRules(int value) { + dataExtractionRules = value; + return this; + } + + @Override public ParsingPackageImpl setIconRes(int value) { iconRes = value; return this; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index ff4cebdd1533..f7f3e19efdf3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -587,6 +587,11 @@ public interface ParsingPackageRead extends Parcelable { */ int getFullBackupContent(); + /** + * @see R.styleable#AndroidManifestApplication_dataExtractionRules + */ + int getDataExtractionRules(); + /** @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS */ boolean isHasDomainUrls(); diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index b7aa30f00691..0c033fddf069 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -2168,6 +2168,8 @@ public class ParsingPackageUtils { .setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa)) .setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa)) .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa)) + .setDataExtractionRules( + resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa)) // Strings .setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa)) .setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa)) diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.aidl b/core/java/android/content/pm/verify/domain/DomainOwner.aidl new file mode 100644 index 000000000000..41366d1a29b2 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.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 android.content.pm.verify.domain; + +parcelable DomainOwner; diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java new file mode 100644 index 000000000000..b050f5da7928 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Set; +import java.util.UUID; + +/** + * @hide + */ +@SystemApi +@DataClass(genParcelable = true, genEqualsHashCode = true, genAidl = true, genToString = true) +public final class DomainOwner implements Parcelable { + + /** + * Package name of that owns the domain. + */ + @NonNull + private final String mPackageName; + + /** + * Whether or not this owner can be automatically overridden. + * + * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean) + */ + private final boolean mOverrideable; + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new DomainOwner. + * + * @param packageName + * Package name of that owns the domain. + * @param overrideable + * Whether or not this owner can be automatically overridden. If all owners for a domain are + * overrideable, then calling + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any + * of the owners are non-overrideable, then + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, + * boolean)} must be called with false to disable all of the other owners before this domain can + * be taken by a new owner through + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)}. + */ + @DataClass.Generated.Member + public DomainOwner( + @NonNull String packageName, + boolean overrideable) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mOverrideable = overrideable; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Package name of that owns the domain. + */ + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * Whether or not this owner can be automatically overridden. If all owners for a domain are + * overrideable, then calling + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any + * of the owners are non-overrideable, then + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, + * boolean)} must be called with false to disable all of the other owners before this domain can + * be taken by a new owner through + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)}. + */ + @DataClass.Generated.Member + public boolean isOverrideable() { + return mOverrideable; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "DomainOwner { " + + "packageName = " + mPackageName + ", " + + "overrideable = " + mOverrideable + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainOwner other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainOwner that = (DomainOwner) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mPackageName, that.mPackageName) + && mOverrideable == that.mOverrideable; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); + _hash = 31 * _hash + Boolean.hashCode(mOverrideable); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mOverrideable) flg |= 0x2; + dest.writeByte(flg); + dest.writeString(mPackageName); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ DomainOwner(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + boolean overrideable = (flg & 0x2) != 0; + String packageName = in.readString(); + + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mOverrideable = overrideable; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<DomainOwner> CREATOR + = new Parcelable.Creator<DomainOwner>() { + @Override + public DomainOwner[] newArray(int size) { + return new DomainOwner[size]; + } + + @Override + public DomainOwner createFromParcel(@NonNull android.os.Parcel in) { + return new DomainOwner(in); + } + }; + + @DataClass.Generated( + time = 1614119379978L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/verify/domain/DomainSet.aidl b/core/java/android/content/pm/verify/domain/DomainSet.aidl new file mode 100644 index 000000000000..fab131dfa317 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.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 android.content.pm.verify.domain; + +parcelable DomainSet; diff --git a/core/java/android/content/pm/verify/domain/DomainSet.java b/core/java/android/content/pm/verify/domain/DomainSet.java new file mode 100644 index 000000000000..243ff0820e24 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Set; + +/** + * Wraps an input set of domains from the client process, to be sent to the server. Handles cases + * where the data size is too large by writing data using {@link Parcel#writeBlob(byte[])}. + * + * @hide + */ +@DataClass(genParcelable = true, genAidl = true, genEqualsHashCode = true) +public class DomainSet implements Parcelable { + + @NonNull + private final Set<String> mDomains; + + private void parcelDomains(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mDomains); + } + + private Set<String> unparcelDomains(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainSet.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public DomainSet( + @NonNull Set<String> domains) { + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull Set<String> getDomains() { + return mDomains; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainSet other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainSet that = (DomainSet) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mDomains, that.mDomains); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mDomains); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + parcelDomains(dest, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected DomainSet(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + Set<String> domains = unparcelDomains(in); + + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<DomainSet> CREATOR + = new Parcelable.Creator<DomainSet>() { + @Override + public DomainSet[] newArray(int size) { + return new DomainSet[size]; + } + + @Override + public DomainSet createFromParcel(@NonNull Parcel in) { + return new DomainSet(in); + } + }; + + @DataClass.Generated( + time = 1613169242020L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainSet.java", + inputSignatures = "private final @android.annotation.NonNull java.util.Set<java.lang.String> mDomains\nprivate void parcelDomains(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelDomains(android.os.Parcel)\nclass DomainSet extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 7afbe1fcb69f..809587524f58 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -19,7 +19,9 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.pm.PackageManager; +import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -34,12 +36,12 @@ import java.util.UUID; * against the digital asset links response from the server hosting that domain. * <p> * These values for each domain can be modified through - * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}. + * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, + * Set, int)}. * * @hide */ @SystemApi -@SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true) public final class DomainVerificationInfo implements Parcelable { @@ -71,22 +73,30 @@ public final class DomainVerificationInfo implements Parcelable { private final String mPackageName; /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @NonNull private final Map<String, Integer> mHostToStateMap; + private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToStateMap); + } + + private Map<String, Integer> unparcelHostToStateMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } + // Code below generated by codegen v1.0.22. @@ -95,7 +105,8 @@ public final class DomainVerificationInfo implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -123,18 +134,17 @@ public final class DomainVerificationInfo implements Parcelable { * @param packageName * The package name that this data corresponds to. * @param hostToStateMap - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. * @hide */ @DataClass.Generated.Member @@ -185,18 +195,17 @@ public final class DomainVerificationInfo implements Parcelable { } /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { @@ -260,13 +269,13 @@ public final class DomainVerificationInfo implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); - dest.writeMap(mHostToStateMap); + parcelHostToStateMap(dest, flags); } @Override @@ -276,14 +285,13 @@ public final class DomainVerificationInfo implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationInfo(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } UUID identifier = sParcellingForIdentifier.unparcel(in); String packageName = in.readString(); - Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>(); - in.readMap(hostToStateMap, Integer.class.getClassLoader()); + Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -307,16 +315,16 @@ public final class DomainVerificationInfo implements Parcelable { } @Override - public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationInfo createFromParcel(@NonNull Parcel in) { return new DomainVerificationInfo(in); } }; @DataClass.Generated( - time = 1611862790369L, + time = 1613002530369L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.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 java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + 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 java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index cbb3baaa6700..11402afac8b6 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -239,7 +239,15 @@ public interface DomainVerificationManager { * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. * * 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. + * if no other app on the device is approved for a higher approval level. This can queried + * using {@link #getOwnersForDomain(String)}. + * + * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to + * enable that domain will disable all other owners. + * + * On the other hand, if any of the owners are non-overrideable, then this must be called with + * false for all of the other owners to disable them before the domain can be taken by a new + * owner. * * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains The domains to toggle the state of. @@ -276,6 +284,19 @@ public interface DomainVerificationManager { throws NameNotFoundException; /** + * For the given domain, return all apps which are approved to open it in a + * greater than 0 priority. This does not mean that all apps can actually open + * an Intent with that domain. That will be decided by the set of apps which + * are the highest priority level, ignoring all lower priority levels. + * + * By default the list will be returned ordered from lowest to highest + * priority. + */ + @NonNull + @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) + List<DomainOwner> getOwnersForDomain(@NonNull String domain); + + /** * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains * provided by the caller is no longer valid. This may be recoverable, and the caller should * re-query the package name associated with the ID using diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java index 5938def5c83c..8b9865c2b436 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java @@ -21,11 +21,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.RemoteException; import android.os.ServiceSpecificException; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; @@ -89,7 +87,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager int state) throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), - new ArrayList<>(domains), state); + new DomainSet(domains), state); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { @@ -126,7 +124,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new ArrayList<>(domains), enabled, mContext.getUserId()); + new DomainSet(domains), enabled, mContext.getUserId()); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { @@ -158,6 +156,16 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager } } + @NonNull + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + try { + return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { return rethrow(exception, domainSetId, null); } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java index 473abce26d81..65f6d7c18135 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java @@ -19,6 +19,7 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Intent; +import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.DataClass; @@ -27,11 +28,11 @@ import com.android.internal.util.Parcelling; import java.util.Set; /** - * Request object sent in the {@link Intent} that's broadcast to the domain verification - * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. + * Request object sent in the {@link Intent} that's broadcast to the domain verification agent, + * retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. * <p> - * This contains the set of packages which have been invalidated and will require - * re-verification. The exact domains can be retrieved with + * This contains the set of packages which have been invalidated and will require re-verification. + * The exact domains can be retrieved with * {@link DomainVerificationManager#getDomainVerificationInfo(String)} * * @hide @@ -42,14 +43,22 @@ import java.util.Set; public final class DomainVerificationRequest implements Parcelable { /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @NonNull @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class) private final Set<String> mPackageNames; + private void parcelPackageNames(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mPackageNames); + } + + private Set<String> unparcelPackageNames(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + // Code below generated by codegen v1.0.22. @@ -58,7 +67,8 @@ public final class DomainVerificationRequest implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -69,9 +79,9 @@ public final class DomainVerificationRequest implements Parcelable { * Creates a new DomainVerificationRequest. * * @param packageNames - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. * @hide */ @DataClass.Generated.Member @@ -85,9 +95,9 @@ public final class DomainVerificationRequest implements Parcelable { } /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @DataClass.Generated.Member public @NonNull Set<String> getPackageNames() { @@ -134,11 +144,11 @@ public final class DomainVerificationRequest implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - sParcellingForPackageNames.parcel(mPackageNames, dest, flags); + parcelPackageNames(dest, flags); } @Override @@ -148,11 +158,11 @@ public final class DomainVerificationRequest implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationRequest(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - Set<String> packageNames = sParcellingForPackageNames.unparcel(in); + Set<String> packageNames = unparcelPackageNames(in); this.mPackageNames = packageNames; com.android.internal.util.AnnotationValidations.validate( @@ -170,16 +180,16 @@ public final class DomainVerificationRequest implements Parcelable { } @Override - public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationRequest createFromParcel(@NonNull Parcel in) { return new DomainVerificationRequest(in); } }; @DataClass.Generated( - time = 1611862814990L, + time = 1613169505495L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nprivate void parcelPackageNames(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelPackageNames(android.os.Parcel)\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java index 73346ef0273b..d23f5f133841 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java @@ -20,8 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -40,29 +42,46 @@ import java.util.UUID; * toggle affects <b>all</b> links and is not based on the verification state of the domains. * <p> * 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. + * to the application to open, which is reflected in {@link #getHostToStateMap()}. 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, - * boolean)} and - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, + * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, * boolean)} APIs. * <p> - * Note that because state is per user, if a different user needs to be changed, one will - * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * Note that because state is per user, if a different user needs to be changed, one will need to + * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link + * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. * * @hide */ @SystemApi @SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, - genEqualsHashCode = true) + genEqualsHashCode = true, genHiddenConstDefs = true) public final class DomainVerificationUserSelection implements Parcelable { /** + * The domain is unverified and unselected, and the application is unable to open web links + * that resolve to the domain. + */ + public static final int DOMAIN_STATE_NONE = 0; + + /** + * The domain has been selected through the + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)} + * API, under the assumption it has not been reset by the system. + */ + public static final int DOMAIN_STATE_SELECTED = 1; + + /** + * The domain has been previously verified by the domain verification agent. + */ + public static final int DOMAIN_STATE_VERIFIED = 2; + + /** * @see DomainVerificationInfo#getIdentifier */ @NonNull @@ -88,15 +107,20 @@ public final class DomainVerificationUserSelection implements Parcelable { private final boolean mLinkHandlingAllowed; /** - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. - * - * @return Map of hosts to enabled state for the given package and user. + * Mapping of domain host to state, as defined by {@link DomainState}. */ @NonNull - private final Map<String, Boolean> mHostToUserSelectionMap; + private final Map<String, Integer> mHostToStateMap; + + private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToStateMap); + } + + @NonNull + private Map<String, Integer> unparcelHostToStateMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } @@ -106,14 +130,37 @@ public final class DomainVerificationUserSelection implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain - // /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 //@formatter:off + /** @hide */ + @android.annotation.IntDef(prefix = "DOMAIN_STATE_", value = { + DOMAIN_STATE_NONE, + DOMAIN_STATE_SELECTED, + DOMAIN_STATE_VERIFIED + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface DomainState {} + + /** @hide */ + @DataClass.Generated.Member + public static String domainStateToString(@DomainState int value) { + switch (value) { + case DOMAIN_STATE_NONE: + return "DOMAIN_STATE_NONE"; + case DOMAIN_STATE_SELECTED: + return "DOMAIN_STATE_SELECTED"; + case DOMAIN_STATE_VERIFIED: + return "DOMAIN_STATE_VERIFIED"; + default: return Integer.toHexString(value); + } + } + /** * Creates a new DomainVerificationUserSelection. * @@ -123,11 +170,8 @@ public final class DomainVerificationUserSelection implements Parcelable { * The user that this data corresponds to. * @param linkHandlingAllowed * Whether or not this package is allowed to open links. - * @param hostToUserSelectionMap - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. + * @param hostToStateMap + * Mapping of domain host to state, as defined by {@link DomainState}. * @hide */ @DataClass.Generated.Member @@ -136,7 +180,7 @@ public final class DomainVerificationUserSelection implements Parcelable { @NonNull String packageName, @NonNull UserHandle user, @NonNull boolean linkHandlingAllowed, - @NonNull Map<String,Boolean> hostToUserSelectionMap) { + @NonNull Map<String,Integer> hostToStateMap) { this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mIdentifier); @@ -149,9 +193,9 @@ public final class DomainVerificationUserSelection implements Parcelable { this.mLinkHandlingAllowed = linkHandlingAllowed; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mLinkHandlingAllowed); - this.mHostToUserSelectionMap = hostToUserSelectionMap; + this.mHostToStateMap = hostToStateMap; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mHostToUserSelectionMap); + NonNull.class, null, mHostToStateMap); // onConstructed(); // You can define this method to get a callback } @@ -189,16 +233,11 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. - * - * @return Map of hosts to enabled state for the given package and user. + * Mapping of domain host to state, as defined by {@link DomainState}. */ @DataClass.Generated.Member - public @NonNull Map<String,Boolean> getHostToUserSelectionMap() { - return mHostToUserSelectionMap; + public @NonNull Map<String,Integer> getHostToStateMap() { + return mHostToStateMap; } @Override @@ -212,7 +251,7 @@ public final class DomainVerificationUserSelection implements Parcelable { "packageName = " + mPackageName + ", " + "user = " + mUser + ", " + "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " + - "hostToUserSelectionMap = " + mHostToUserSelectionMap + + "hostToStateMap = " + mHostToStateMap + " }"; } @@ -233,7 +272,7 @@ public final class DomainVerificationUserSelection implements Parcelable { && java.util.Objects.equals(mPackageName, that.mPackageName) && java.util.Objects.equals(mUser, that.mUser) && mLinkHandlingAllowed == that.mLinkHandlingAllowed - && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap); + && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap); } @Override @@ -247,7 +286,7 @@ public final class DomainVerificationUserSelection implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); _hash = 31 * _hash + java.util.Objects.hashCode(mUser); _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed); - _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap); return _hash; } @@ -264,7 +303,7 @@ public final class DomainVerificationUserSelection implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -274,7 +313,7 @@ public final class DomainVerificationUserSelection implements Parcelable { sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); dest.writeTypedObject(mUser, flags); - dest.writeMap(mHostToUserSelectionMap); + parcelHostToStateMap(dest, flags); } @Override @@ -284,7 +323,7 @@ public final class DomainVerificationUserSelection implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -293,8 +332,7 @@ public final class DomainVerificationUserSelection implements Parcelable { UUID identifier = sParcellingForIdentifier.unparcel(in); String packageName = in.readString(); UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR); - Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>(); - in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader()); + Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -308,9 +346,9 @@ public final class DomainVerificationUserSelection implements Parcelable { this.mLinkHandlingAllowed = linkHandlingAllowed; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mLinkHandlingAllowed); - this.mHostToUserSelectionMap = hostToUserSelectionMap; + this.mHostToStateMap = hostToStateMap; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mHostToUserSelectionMap); + NonNull.class, null, mHostToStateMap); // onConstructed(); // You can define this method to get a callback } @@ -324,16 +362,16 @@ public final class DomainVerificationUserSelection implements Parcelable { } @Override - public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) { return new DomainVerificationUserSelection(in); } }; @DataClass.Generated( - time = 1612829797220L, + time = 1613683603297L, codegenVersion = "1.0.22", 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)") + inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate 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.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\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, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java new file mode 100644 index 000000000000..93005fae1772 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.Parcel; +import android.util.ArraySet; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * @hide + */ +public class DomainVerificationUtils { + + private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2; + + /** + * Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])} + * if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the + * written map is the only data structure in the caller that varies based on the host data set. + * Other data that will be written to the parcel after this method will not be considered in the + * calculation. + */ + public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : map.keySet()) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + dest.writeMap(map); + return; + } + + Parcel data = Parcel.obtain(); + try { + data.writeMap(map); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}. + */ + @NonNull + @SuppressWarnings("rawtypes") + public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map, + @NonNull ClassLoader classLoader) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + in.readMap(map, classLoader); + return map; + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + data.readMap(map, classLoader); + } finally { + data.recycle(); + } + + return map; + } + + /** + * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}. + */ + public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : set) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + writeSet(dest, set); + return; + } + + Parcel data = Parcel.obtain(); + try { + writeSet(data, set); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}. + */ + @NonNull + public static Set<String> readHostSet(@NonNull Parcel in) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + return readSet(in); + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + return readSet(data); + } finally { + data.recycle(); + } + } + + private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) { + if (set == null) { + dest.writeInt(-1); + return; + } + dest.writeInt(set.size()); + for (String string : set) { + dest.writeString(string); + } + } + + @NonNull + private static Set<String> readSet(@NonNull Parcel in) { + int size = in.readInt(); + if (size == -1) { + return Collections.emptySet(); + } + + ArraySet<String> set = new ArraySet<>(size); + for (int count = 0; count < size; count++) { + set.add(in.readString()); + } + return set; + } + + /** + * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains + * across the client-server API. + */ + public static int estimatedByteSizeOf(String string) { + return string.length() * 2 + 12; + } +} diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 21dd623b46bc..701af320fb01 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -16,6 +16,8 @@ package android.content.pm.verify.domain; +import android.content.pm.verify.domain.DomainOwner; +import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationUserSelection; import java.util.List; @@ -35,10 +37,13 @@ interface IDomainVerificationManager { DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, int userId); - void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state); + @nullable + List<DomainOwner> getOwnersForDomain(String domain, int userId); + + void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); - void setDomainVerificationUserSelection(String domainSetId, in List<String> domains, + void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, boolean enabled, int userId); } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 2f7aeb80986b..b66f048b829d 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1956,7 +1956,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(mnc); fixUpLocaleList(); - dest.writeParcelable(mLocaleList, flags); + dest.writeTypedObject(mLocaleList, flags); if(userSetLocale) { dest.writeInt(1); @@ -1980,7 +1980,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(compatScreenWidthDp); dest.writeInt(compatScreenHeightDp); dest.writeInt(compatSmallestScreenWidthDp); - dest.writeValue(windowConfiguration); + windowConfiguration.writeToParcel(dest, flags); dest.writeInt(assetsSeq); dest.writeInt(seq); dest.writeInt(fontWeightAdjustment); @@ -1991,7 +1991,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration mcc = source.readInt(); mnc = source.readInt(); - mLocaleList = source.readParcelable(LocaleList.class.getClassLoader()); + mLocaleList = source.readTypedObject(LocaleList.CREATOR); locale = mLocaleList.get(0); userSetLocale = (source.readInt()==1); @@ -2012,7 +2012,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatScreenWidthDp = source.readInt(); compatScreenHeightDp = source.readInt(); compatSmallestScreenWidthDp = source.readInt(); - windowConfiguration.setTo((WindowConfiguration) source.readValue(null)); + windowConfiguration.readFromParcel(source); assetsSeq = source.readInt(); seq = source.readInt(); fontWeightAdjustment = source.readInt(); diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index e512cf1bbb1f..429eef95f952 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -20,7 +20,6 @@ import android.Manifest; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -29,7 +28,6 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.text.FontConfig; -import android.util.Log; import com.android.internal.graphics.fonts.IFontManager; @@ -198,12 +196,11 @@ public class FontManager { * @return The current font configuration. null if failed to fetch information from the system * service. */ - public @Nullable FontConfig getFontConfig() { + public @NonNull FontConfig getFontConfig() { try { return mIFontManager.getFontConfig(); } catch (RemoteException e) { - Log.e(TAG, "Failed to call getFontConfig", e); - return null; + throw e.rethrowAsRuntimeException(); } } diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index f4f9e1775d1a..e03c1f48773a 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -19,6 +19,7 @@ package android.hardware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; @@ -33,6 +34,7 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * This class provides access to the sensor privacy services; sensor privacy allows the @@ -42,9 +44,21 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi @TestApi @SystemService(Context.SENSOR_PRIVACY_SERVICE) public final class SensorPrivacyManager { + + /** + * @hide + */ + public static final boolean USE_MICROPHONE_TOGGLE = true; + + /** + * @hide + */ + public static final boolean USE_CAMERA_TOGGLE = true; + /** * Unique Id of this manager to identify to the service * @hide @@ -58,28 +72,39 @@ public final class SensorPrivacyManager { public static final String EXTRA_SENSOR = SensorPrivacyManager.class.getName() + ".extra.sensor"; - /** Microphone - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_MICROPHONE = - SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; - - /** Camera - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_CAMERA = - SensorPrivacyIndividualEnabledSensorProto.CAMERA; - /** - * Individual sensors not listed in {@link Sensor} + * Individual sensors not listed in {@link Sensors} * @hide */ - @IntDef(prefix = "INDIVIDUAL_SENSOR_", value = { - INDIVIDUAL_SENSOR_MICROPHONE, - INDIVIDUAL_SENSOR_CAMERA - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndividualSensor {} + @SystemApi + @TestApi + public static class Sensors { + + private Sensors() {} + + /** Microphone + * @hide */ + @SystemApi + @TestApi + public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; + /** Camera + * @hide */ + @SystemApi + @TestApi + public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA; + + /** + * Individual sensors not listed in {@link Sensors} + * + * @hide + */ + @IntDef(value = { + MICROPHONE, + CAMERA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Sensor {} + } /** * A class implementing this interface can register with the {@link @@ -88,6 +113,8 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi + @TestApi public interface OnSensorPrivacyChangedListener { /** * Callback invoked when the sensor privacy state changes. @@ -165,7 +192,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@NonNull final OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { @@ -196,15 +224,37 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(@IndividualSensor int sensor, - final OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, + @NonNull OnSensorPrivacyChangedListener listener) { + addSensorPrivacyListener(sensor, mContext.getMainExecutor(), listener); + } + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param sensor the sensor to listen to changes to + * @param executor the executor to dispatch the callback on + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + * + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor, + @NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { - listener.onSensorPrivacyChanged(enabled); + executor.execute(() -> listener.onSensorPrivacyChanged(enabled)); } }; mListeners.put(listener, iListener); @@ -228,7 +278,10 @@ public final class SensorPrivacyManager { * * @hide */ - public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void removeSensorPrivacyListener(@NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener != null) { @@ -249,6 +302,7 @@ public final class SensorPrivacyManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled() { try { return mService.isSensorPrivacyEnabled(); @@ -264,8 +318,10 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi @TestApi - public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) { try { return mService.isIndividualSensorPrivacyEnabled(mContext.getUserId(), sensor); } catch (RemoteException e) { @@ -283,8 +339,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacy(@IndividualSensor int sensor, - boolean enable) { + public void setSensorPrivacy(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacy(mContext.getUserId(), sensor, enable); } catch (RemoteException e) { @@ -303,7 +358,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacyForProfileGroup(@IndividualSensor int sensor, + public void setSensorPrivacyForProfileGroup(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacyForProfileGroup(mContext.getUserId(), sensor, @@ -321,7 +376,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void suppressIndividualSensorPrivacyReminders(@NonNull String packageName, + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void suppressSensorPrivacyReminders(@NonNull String packageName, boolean suppress) { try { mService.suppressIndividualSensorPrivacyReminders(mContext.getUserId(), packageName, diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 76d50bdf414c..43ef33e1f420 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -145,6 +145,12 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** + * Authentication cannot proceed because re-enrollment is required. + * @hide + */ + int BIOMETRIC_ERROR_RE_ENROLL = 16; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index eafcf529de62..4385b1dac7a0 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -153,6 +153,12 @@ public interface BiometricFaceConstants { int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** + * Authentication cannot proceed because re-enrollment is required. + * @hide + */ + int BIOMETRIC_ERROR_RE_ENROLL = 16; + + /** * @hide */ int FACE_ERROR_VENDOR_BASE = 1000; diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 01f0e71a7c33..30e24d2ec8db 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -166,6 +166,12 @@ public interface BiometricFingerprintConstants { public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** + * Authentication cannot proceed because re-enrollment is required. + * @hide + */ + int BIOMETRIC_ERROR_RE_ENROLL = 16; + + /** * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index f9eecaec5bb2..ac6ba0a4ac58 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -378,9 +378,10 @@ public abstract class CameraDevice implements AutoCloseable { * released, continuous repeating requests stopped and any pending * multi-frame capture requests flushed.</p> * - * <p>Note that the CameraExtensionSession currently supports at most two - * multi frame capture surface formats: ImageFormat.YUV_420_888 and - * ImageFormat.JPEG. Clients must query the multi-frame capture format support using + * <p>Note that the CameraExtensionSession currently supports at most wo + * multi frame capture surface formats: ImageFormat.JPEG will be supported + * by all extensions and ImageFormat.YUV_420_888 may or may not be supported. + * Clients must query the multi-frame capture format support using * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)}. * For repeating requests CameraExtensionSession supports only * {@link android.graphics.SurfaceTexture} as output. Clients can query the supported resolution diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index d3eb3779189b..6121cd260dc3 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -35,6 +35,7 @@ import android.util.Log; import android.util.Pair; import android.util.Size; +import java.util.HashSet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -153,12 +154,8 @@ public final class CameraExtensionCharacteristics { mChars = chars; } - private static List<Size> generateSupportedSizes(List<SizeList> sizesList, - Integer format, - StreamConfigurationMap streamMap) { - // Per API contract it is assumed that the extension is able to support all - // camera advertised sizes for a given format in case it doesn't return - // a valid non-empty size list. + private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList, + Integer format) { ArrayList<Size> ret = new ArrayList<>(); if ((sizesList != null) && (!sizesList.isEmpty())) { for (SizeList entry : sizesList) { @@ -170,13 +167,36 @@ public final class CameraExtensionCharacteristics { } } } + + return ret; + } + + private static List<Size> generateSupportedSizes(List<SizeList> sizesList, + Integer format, + StreamConfigurationMap streamMap) { + // Per API contract it is assumed that the extension is able to support all + // camera advertised sizes for a given format in case it doesn't return + // a valid non-empty size list. + ArrayList<Size> ret = getSupportedSizes(sizesList, format); Size[] supportedSizes = streamMap.getOutputSizes(format); - if (supportedSizes != null) { + if ((ret.isEmpty()) && (supportedSizes != null)) { ret.addAll(Arrays.asList(supportedSizes)); } return ret; } + private static List<Size> generateJpegSupportedSizes(List<SizeList> sizesList, + StreamConfigurationMap streamMap) { + ArrayList<Size> extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888); + HashSet<Size> supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList( + streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes); + HashSet<Size> supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes( + ImageFormat.JPEG))); + supportedSizes.retainAll(supportedJpegSizes); + + return new ArrayList<>(supportedSizes); + } + /** * A per-process global camera extension manager instance, to track and * initialize/release extensions depending on client activity. @@ -488,8 +508,8 @@ public final class CameraExtensionCharacteristics { * {@link StreamConfigurationMap#getOutputSizes}.</p> * * <p>Device-specific extensions currently support at most two - * multi-frame capture surface formats, ImageFormat.YUV_420_888 or - * ImageFormat.JPEG.</p> + * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all + * extensions and ImageFormat.YUV_420_888 may or may not be supported.</p> * * @param extension the extension type * @param format device-specific extension output format @@ -526,14 +546,17 @@ public final class CameraExtensionCharacteristics { format, streamMap); } else if (format == ImageFormat.JPEG) { extenders.second.init(mCameraId, mChars.getNativeMetadata()); - if (extenders.second.getCaptureProcessor() == null) { + if (extenders.second.getCaptureProcessor() != null) { + // The framework will perform the additional encoding pass on the + // processed YUV_420 buffers. + return generateJpegSupportedSizes( + extenders.second.getSupportedResolutions(), streamMap); + } else { return generateSupportedSizes(null, format, streamMap); } - - return new ArrayList<>(); + } else { + throw new IllegalArgumentException("Unsupported format: " + format); } - - throw new IllegalArgumentException("Unsupported format: " + format); } finally { unregisterClient(clientId); } diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java index 877dfbc49df2..e1b817768461 100644 --- a/core/java/android/hardware/camera2/CameraExtensionSession.java +++ b/core/java/android/hardware/camera2/CameraExtensionSession.java @@ -238,8 +238,10 @@ public abstract class CameraExtensionSession implements AutoCloseable { * from the camera device, to produce a single high-quality output result. * * <p>Note that single capture requests currently do not support - * client parameters. Settings included in the request will - * be entirely overridden by the device-specific extension. </p> + * client parameters except for {@link CaptureRequest#JPEG_ORIENTATION orientation} and + * {@link CaptureRequest#JPEG_QUALITY quality} in case of ImageFormat.JPEG output target. + * The rest of the settings included in the request will be entirely overridden by + * the device-specific extension. </p> * * <p>The {@link CaptureRequest.Builder#addTarget} supports only one * ImageFormat.YUV_420_888 or ImageFormat.JPEG target surface. {@link CaptureRequest} diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java new file mode 100644 index 000000000000..936734b0c711 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java @@ -0,0 +1,312 @@ +/* + * 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.hardware.camera2.impl; + +import android.annotation.NonNull; +import android.graphics.ImageFormat; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.extension.CaptureBundle; +import android.hardware.camera2.extension.ICaptureProcessorImpl; +import android.media.Image; +import android.media.Image.Plane; +import android.media.ImageReader; +import android.media.ImageWriter; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +// Jpeg compress input YUV and queue back in the client target surface. +public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { + public final static String TAG = "CameraExtensionJpeg"; + private final static int JPEG_QUEUE_SIZE = 1; + private final static int JPEG_DEFAULT_QUALITY = 100; + private final static int JPEG_DEFAULT_ROTATION = 0; + + private final Handler mHandler; + private final HandlerThread mHandlerThread; + private final ICaptureProcessorImpl mProcessor; + + private ImageReader mYuvReader = null; + private android.hardware.camera2.extension.Size mResolution = null; + private int mFormat = -1; + private Surface mOutputSurface = null; + private ImageWriter mOutputWriter = null; + + private static final class JpegParameters { + public HashSet<Long> mTimeStamps = new HashSet<>(); + public int mRotation = JPEG_DEFAULT_ROTATION; // CCW multiple of 90 degrees + public int mQuality = JPEG_DEFAULT_QUALITY; // [0..100] + } + + private ConcurrentLinkedQueue<JpegParameters> mJpegParameters = new ConcurrentLinkedQueue<>(); + + public CameraExtensionJpegProcessor(@NonNull ICaptureProcessorImpl processor) { + mProcessor = processor; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + + public void close() { + mHandlerThread.quitSafely(); + + if (mOutputWriter != null) { + mOutputWriter.close(); + mOutputWriter = null; + } + + if (mYuvReader != null) { + mYuvReader.close(); + mYuvReader = null; + } + } + + private static JpegParameters getJpegParameters(List<CaptureBundle> captureBundles) { + JpegParameters ret = new JpegParameters(); + if (!captureBundles.isEmpty()) { + // The quality and orientation settings must be equal for requests in a burst + + Byte jpegQuality = captureBundles.get(0).captureResult.get(CaptureResult.JPEG_QUALITY); + if (jpegQuality != null) { + ret.mQuality = jpegQuality; + } else { + Log.w(TAG, "No jpeg quality set, using default: " + JPEG_DEFAULT_QUALITY); + } + + Integer orientation = captureBundles.get(0).captureResult.get( + CaptureResult.JPEG_ORIENTATION); + if (orientation != null) { + ret.mRotation = orientation / 90; + } else { + Log.w(TAG, "No jpeg rotation set, using default: " + JPEG_DEFAULT_ROTATION); + } + + for (CaptureBundle bundle : captureBundles) { + Long timeStamp = bundle.captureResult.get(CaptureResult.SENSOR_TIMESTAMP); + if (timeStamp != null) { + ret.mTimeStamps.add(timeStamp); + } else { + Log.e(TAG, "Capture bundle without valid sensor timestamp!"); + } + } + } + + return ret; + } + + /** + * Compresses a YCbCr image to jpeg, applying a crop and rotation. + * <p> + * The input is defined as a set of 3 planes of 8-bit samples, one plane for + * each channel of Y, Cb, Cr.<br> + * The Y plane is assumed to have the same width and height of the entire + * image.<br> + * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to + * have dimensions (floor(width / 2), floor(height / 2)).<br> + * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride, + * and a row-stride. So, the sample at coordinate (x, y) can be retrieved + * from byteBuffer[x * pixel_stride + y * row_stride]. + * <p> + * The pre-compression transformation is applied as follows: + * <ol> + * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to + * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) - + * (width, height) is a no-op.</li> + * <li>The rotation is applied counter-clockwise relative to the coordinate + * space of the image, so a CCW rotation will appear CW when the image is + * rendered in scanline order. Only rotations which are multiples of + * 90-degrees are suppored, so the parameter 'rot90' specifies which + * multiple of 90 to rotate the image.</li> + * </ol> + * + * @param width the width of the image to compress + * @param height the height of the image to compress + * @param yBuf the buffer containing the Y component of the image + * @param yPStride the stride between adjacent pixels in the same row in + * yBuf + * @param yRStride the stride between adjacent rows in yBuf + * @param cbBuf the buffer containing the Cb component of the image + * @param cbPStride the stride between adjacent pixels in the same row in + * cbBuf + * @param cbRStride the stride between adjacent rows in cbBuf + * @param crBuf the buffer containing the Cr component of the image + * @param crPStride the stride between adjacent pixels in the same row in + * crBuf + * @param crRStride the stride between adjacent rows in crBuf + * @param outBuf a direct java.nio.ByteBuffer to hold the compressed jpeg. + * This must have enough capacity to store the result, or an + * error code will be returned. + * @param outBufCapacity the capacity of outBuf + * @param quality the jpeg-quality (1-100) to use + * @param cropLeft left-edge of the bounds of the image to crop to before + * rotation + * @param cropTop top-edge of the bounds of the image to crop to before + * rotation + * @param cropRight right-edge of the bounds of the image to crop to before + * rotation + * @param cropBottom bottom-edge of the bounds of the image to crop to + * before rotation + * @param rot90 the multiple of 90 to rotate the image CCW (after cropping) + */ + private static native int compressJpegFromYUV420pNative( + int width, int height, + ByteBuffer yBuf, int yPStride, int yRStride, + ByteBuffer cbBuf, int cbPStride, int cbRStride, + ByteBuffer crBuf, int crPStride, int crRStride, + ByteBuffer outBuf, int outBufCapacity, + int quality, + int cropLeft, int cropTop, int cropRight, int cropBottom, + int rot90); + + public void process(List<CaptureBundle> captureBundle) throws RemoteException { + JpegParameters jpegParams = getJpegParameters(captureBundle); + try { + mJpegParameters.add(jpegParams); + mProcessor.process(captureBundle); + } catch (Exception e) { + mJpegParameters.remove(jpegParams); + throw e; + } + } + + public void onOutputSurface(Surface surface, int format) throws RemoteException { + if (format != ImageFormat.JPEG) { + Log.e(TAG, "Unsupported output format: " + format); + return; + } + mOutputSurface = surface; + initializePipeline(); + } + + @Override + public void onResolutionUpdate(android.hardware.camera2.extension.Size size) + throws RemoteException { + mResolution = size; + initializePipeline(); + } + + public void onImageFormatUpdate(int format) throws RemoteException { + if (format != ImageFormat.YUV_420_888) { + Log.e(TAG, "Unsupported input format: " + format); + return; + } + mFormat = format; + initializePipeline(); + } + + private void initializePipeline() throws RemoteException { + if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) && + (mYuvReader == null)) { + // Jpeg/blobs are expected to be configured with (w*h)x1 + mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/, + ImageFormat.JPEG, mResolution.width * mResolution.height, 1); + mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat, + JPEG_QUEUE_SIZE); + mYuvReader.setOnImageAvailableListener(new YuvCallback(), mHandler); + mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat); + mProcessor.onResolutionUpdate(mResolution); + mProcessor.onImageFormatUpdate(mFormat); + } + } + + @Override + public IBinder asBinder() { + throw new UnsupportedOperationException("Binder IPC not supported!"); + } + + private class YuvCallback implements ImageReader.OnImageAvailableListener { + @Override + public void onImageAvailable(ImageReader reader) { + Image yuvImage = null; + Image jpegImage = null; + try { + yuvImage = mYuvReader.acquireNextImage(); + jpegImage = mOutputWriter.dequeueInputImage(); + } catch (IllegalStateException e) { + if (yuvImage != null) { + yuvImage.close(); + } + if (jpegImage != null) { + jpegImage.close(); + } + Log.e(TAG, "Failed to acquire processed yuv image or jpeg image!"); + return; + } + + ByteBuffer jpegBuffer = jpegImage.getPlanes()[0].getBuffer(); + jpegBuffer.clear(); + // Jpeg/blobs are expected to be configured with (w*h)x1 + int jpegCapacity = jpegImage.getWidth(); + + Plane lumaPlane = yuvImage.getPlanes()[0]; + Plane crPlane = yuvImage.getPlanes()[1]; + Plane cbPlane = yuvImage.getPlanes()[2]; + + Iterator<JpegParameters> jpegIter = mJpegParameters.iterator(); + JpegParameters jpegParams = null; + while(jpegIter.hasNext()) { + JpegParameters currentParams = jpegIter.next(); + if (currentParams.mTimeStamps.contains(yuvImage.getTimestamp())) { + jpegParams = currentParams; + jpegIter.remove(); + break; + } + } + if (jpegParams == null) { + if (mJpegParameters.isEmpty()) { + Log.w(TAG, "Empty jpeg settings queue! Using default jpeg orientation" + + " and quality!"); + jpegParams = new JpegParameters(); + jpegParams.mRotation = JPEG_DEFAULT_ROTATION; + jpegParams.mQuality = JPEG_DEFAULT_QUALITY; + } else { + Log.w(TAG, "No jpeg settings found with matching timestamp for current" + + " processed input!"); + Log.w(TAG, "Using values from the top of the queue!"); + jpegParams = mJpegParameters.poll(); + } + } + + compressJpegFromYUV420pNative( + yuvImage.getWidth(), yuvImage.getHeight(), + lumaPlane.getBuffer(), lumaPlane.getPixelStride(), lumaPlane.getRowStride(), + crPlane.getBuffer(), crPlane.getPixelStride(), crPlane.getRowStride(), + cbPlane.getBuffer(), cbPlane.getPixelStride(), cbPlane.getRowStride(), + jpegBuffer, jpegCapacity, jpegParams.mQuality, + 0, 0, yuvImage.getWidth(), yuvImage.getHeight(), + jpegParams.mRotation); + yuvImage.close(); + + try { + mOutputWriter.queueInputImage(jpegImage); + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to queue encoded result!"); + } finally { + jpegImage.close(); + } + } + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 8451dedb6c37..0a561716d076 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -91,6 +91,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private ImageReader mStubCaptureImageReader = null; private ImageWriter mRepeatingRequestImageWriter = null; + private CameraExtensionJpegProcessor mImageJpegProcessor = null; private ICaptureProcessorImpl mImageProcessor = null; private CameraExtensionForwardProcessor mPreviewImageProcessor = null; private IRequestUpdateProcessorImpl mPreviewRequestUpdateProcessor = null; @@ -413,6 +414,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { if (mImageProcessor != null) { if (mClientCaptureSurface != null) { SurfaceInfo surfaceInfo = querySurface(mClientCaptureSurface); + if (surfaceInfo.mFormat == ImageFormat.JPEG) { + mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor); + mImageProcessor = mImageJpegProcessor; + } mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth, surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, mImageExtender.getMaxCaptureStage()); @@ -570,14 +575,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return null; } - // Set user supported jpeg quality and rotation parameters + // This will override the extension capture stage jpeg parameters with the user set + // jpeg quality and rotation. This will guarantee that client configured jpeg + // parameters always have highest priority. Integer jpegRotation = clientRequest.get(CaptureRequest.JPEG_ORIENTATION); if (jpegRotation != null) { - requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation); + captureStage.parameters.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation); } Byte jpegQuality = clientRequest.get(CaptureRequest.JPEG_QUALITY); if (jpegQuality != null) { - requestBuilder.set(CaptureRequest.JPEG_QUALITY, jpegQuality); + captureStage.parameters.set(CaptureRequest.JPEG_QUALITY, jpegQuality); } requestBuilder.addTarget(target); @@ -753,6 +760,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mPreviewImageProcessor = null; } + if (mImageJpegProcessor != null) { + mImageJpegProcessor.close(); + mImageJpegProcessor = null; + } + mCaptureSession = null; mImageProcessor = null; mCameraRepeatingSurface = mClientRepeatingRequestSurface = null; @@ -1014,7 +1026,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mCaptureRequestMap.clear(); mCapturePendingMap.clear(); boolean processStatus = true; - List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap); + Byte jpegQuality = mClientRequest.get(CaptureRequest.JPEG_QUALITY); + Integer jpegOrientation = mClientRequest.get(CaptureRequest.JPEG_ORIENTATION); + List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap, + jpegOrientation, jpegQuality); try { mImageProcessor.process(captureList); } catch (RemoteException e) { @@ -1444,10 +1459,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } for (int i = idx; i >= 0; i--) { if (previewMap.valueAt(i).first != null) { - Log.w(TAG, "Discard pending buffer with timestamp: " + previewMap.keyAt(i)); previewMap.valueAt(i).first.close(); } else { - Log.w(TAG, "Discard pending result with timestamp: " + previewMap.keyAt(i)); if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) { Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i)); final long ident = Binder.clearCallingIdentity(); @@ -1639,7 +1652,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } private static List<CaptureBundle> initializeParcelable( - HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap) { + HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap, Integer jpegOrientation, + Byte jpegQuality) { ArrayList<CaptureBundle> ret = new ArrayList<>(); for (Integer stagetId : captureMap.keySet()) { Pair<Image, TotalCaptureResult> entry = captureMap.get(stagetId); @@ -1648,6 +1662,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { bundle.captureImage = initializeParcelImage(entry.first); bundle.sequenceId = entry.second.getSequenceId(); bundle.captureResult = entry.second.getNativeMetadata(); + if (jpegOrientation != null) { + bundle.captureResult.set(CaptureResult.JPEG_ORIENTATION, jpegOrientation); + } + if (jpegQuality != null) { + bundle.captureResult.set(CaptureResult.JPEG_QUALITY, jpegQuality); + } ret.add(bundle); } diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java index 9457d8f1aac4..41126b70c89f 100644 --- a/core/java/android/hardware/display/DeviceProductInfo.java +++ b/core/java/android/hardware/display/DeviceProductInfo.java @@ -16,69 +16,40 @@ package android.hardware.display; -import android.annotation.IntDef; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.Objects; /** * Product-specific information about the display or the directly connected device on the * display chain. For example, if the display is transitively connected, this field may contain * product information about the intermediate device. + * @hide */ public final class DeviceProductInfo implements Parcelable { - /** @hide */ - @IntDef(prefix = {"CONNECTION_TO_SINK_"}, value = { - CONNECTION_TO_SINK_UNKNOWN, - CONNECTION_TO_SINK_BUILT_IN, - CONNECTION_TO_SINK_DIRECT, - CONNECTION_TO_SINK_TRANSITIVE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ConnectionToSinkType { } - - /** The device connection to the display sink is unknown. */ - public static final int CONNECTION_TO_SINK_UNKNOWN = - IDeviceProductInfoConstants.CONNECTION_TO_SINK_UNKNOWN; - - /** The display sink is built-in to the device */ - public static final int CONNECTION_TO_SINK_BUILT_IN = - IDeviceProductInfoConstants.CONNECTION_TO_SINK_BUILT_IN; - - /** The device is directly connected to the display sink. */ - public static final int CONNECTION_TO_SINK_DIRECT = - IDeviceProductInfoConstants.CONNECTION_TO_SINK_DIRECT; - - /** The device is transitively connected to the display sink. */ - public static final int CONNECTION_TO_SINK_TRANSITIVE = - IDeviceProductInfoConstants.CONNECTION_TO_SINK_TRANSITIVE; - private final String mName; private final String mManufacturerPnpId; private final String mProductId; private final Integer mModelYear; private final ManufactureDate mManufactureDate; - private final @ConnectionToSinkType int mConnectionToSinkType; + private final int[] mRelativeAddress; - /** @hide */ public DeviceProductInfo( String name, String manufacturerPnpId, String productId, Integer modelYear, ManufactureDate manufactureDate, - int connectionToSinkType) { + int[] relativeAddress) { this.mName = name; this.mManufacturerPnpId = manufacturerPnpId; this.mProductId = productId; this.mModelYear = modelYear; this.mManufactureDate = manufactureDate; - this.mConnectionToSinkType = connectionToSinkType; + this.mRelativeAddress = relativeAddress; } private DeviceProductInfo(Parcel in) { @@ -87,13 +58,12 @@ public final class DeviceProductInfo implements Parcelable { mProductId = (String) in.readValue(null); mModelYear = (Integer) in.readValue(null); mManufactureDate = (ManufactureDate) in.readValue(null); - mConnectionToSinkType = in.readInt(); + mRelativeAddress = in.createIntArray(); } /** * @return Display name. */ - @Nullable public String getName() { return mName; } @@ -101,7 +71,6 @@ public final class DeviceProductInfo implements Parcelable { /** * @return Manufacturer Plug and Play ID. */ - @NonNull public String getManufacturerPnpId() { return mManufacturerPnpId; } @@ -109,58 +78,32 @@ public final class DeviceProductInfo implements Parcelable { /** * @return Manufacturer product ID. */ - @NonNull public String getProductId() { return mProductId; } /** - * @return Model year of the device. Return -1 if not available. Typically, - * one of model year or manufacture year is available. + * @return Model year of the device. Typically exactly one of model year or + * manufacture date will be present. */ - public int getModelYear() { - return mModelYear != null ? mModelYear : -1; - } - - /** - * @return The year of manufacture, or -1 it is not available. Typically, - * one of model year or manufacture year is available. - */ - public int getManufactureYear() { - if (mManufactureDate == null) { - return -1; - } - return mManufactureDate.mYear != null ? mManufactureDate.mYear : -1; - } - - /** - * @return The week of manufacture, or -1 it is not available. Typically, - * not present if model year is available. - */ - public int getManufactureWeek() { - if (mManufactureDate == null) { - return -1; - } - return mManufactureDate.mWeek != null ? mManufactureDate.mWeek : -1; + public Integer getModelYear() { + return mModelYear; } /** * @return Manufacture date. Typically exactly one of model year or manufacture * date will be present. - * - * @hide */ public ManufactureDate getManufactureDate() { return mManufactureDate; } /** - * @return How the current device is connected to the display sink. For example, the display - * can be connected immediately to the device or there can be a receiver in between. + * @return Relative address in the display network. For example, for HDMI connected devices this + * can be its physical address. Each component of the address is in the range [0, 255]. */ - @ConnectionToSinkType - public int getConnectionToSinkType() { - return mConnectionToSinkType; + public int[] getRelativeAddress() { + return mRelativeAddress; } @Override @@ -176,8 +119,8 @@ public final class DeviceProductInfo implements Parcelable { + mModelYear + ", manufactureDate=" + mManufactureDate - + ", connectionToSinkType=" - + mConnectionToSinkType + + ", relativeAddress=" + + Arrays.toString(mRelativeAddress) + '}'; } @@ -191,16 +134,16 @@ public final class DeviceProductInfo implements Parcelable { && Objects.equals(mProductId, that.mProductId) && Objects.equals(mModelYear, that.mModelYear) && Objects.equals(mManufactureDate, that.mManufactureDate) - && mConnectionToSinkType == that.mConnectionToSinkType; + && Arrays.equals(mRelativeAddress, that.mRelativeAddress); } @Override public int hashCode() { return Objects.hash(mName, mManufacturerPnpId, mProductId, mModelYear, mManufactureDate, - mConnectionToSinkType); + Arrays.hashCode(mRelativeAddress)); } - @NonNull public static final Creator<DeviceProductInfo> CREATOR = + public static final Creator<DeviceProductInfo> CREATOR = new Creator<DeviceProductInfo>() { @Override public DeviceProductInfo createFromParcel(Parcel in) { @@ -219,13 +162,13 @@ public final class DeviceProductInfo implements Parcelable { } @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { + public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mManufacturerPnpId); dest.writeValue(mProductId); dest.writeValue(mModelYear); dest.writeValue(mManufactureDate); - dest.writeInt(mConnectionToSinkType); + dest.writeIntArray(mRelativeAddress); } /** diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index a9bcdeff7e47..0256b7bc6de0 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -831,6 +831,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: return context.getString( com.android.internal.R.string.face_error_security_update_required); + case BIOMETRIC_ERROR_RE_ENROLL: + return context.getString( + com.android.internal.R.string.face_recalibrate_notification_content); case FACE_ERROR_VENDOR: { String[] msgArray = context.getResources().getStringArray( com.android.internal.R.array.face_error_vendor); @@ -1389,7 +1392,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case FACE_ACQUIRED_PAN_TOO_EXTREME: case FACE_ACQUIRED_TILT_TOO_EXTREME: case FACE_ACQUIRED_ROLL_TOO_EXTREME: - return context.getString(R.string.face_acquired_not_detected); + return context.getString(R.string.face_acquired_poor_gaze); // Provide more detailed feedback for other soft errors. case FACE_ACQUIRED_INSUFFICIENT: @@ -1445,13 +1448,17 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case FACE_ACQUIRED_TOO_FAR: return context.getString(R.string.face_acquired_too_far); case FACE_ACQUIRED_TOO_HIGH: - return context.getString(R.string.face_acquired_too_high); - case FACE_ACQUIRED_TOO_LOW: + // TODO(b/181269243): Change back once error codes are fixed. return context.getString(R.string.face_acquired_too_low); + case FACE_ACQUIRED_TOO_LOW: + // TODO(b/181269243) Change back once error codes are fixed. + return context.getString(R.string.face_acquired_too_high); case FACE_ACQUIRED_TOO_RIGHT: - return context.getString(R.string.face_acquired_too_right); - case FACE_ACQUIRED_TOO_LEFT: + // TODO(b/181269243) Change back once error codes are fixed. return context.getString(R.string.face_acquired_too_left); + case FACE_ACQUIRED_TOO_LEFT: + // TODO(b/181269243) Change back once error codes are fixed. + return context.getString(R.string.face_acquired_too_right); case FACE_ACQUIRED_POOR_GAZE: return context.getString(R.string.face_acquired_poor_gaze); case FACE_ACQUIRED_NOT_DETECTED: diff --git a/core/java/android/hardware/input/OWNERS b/core/java/android/hardware/input/OWNERS index 25e02e1aa6f3..c390b33fa174 100644 --- a/core/java/android/hardware/input/OWNERS +++ b/core/java/android/hardware/input/OWNERS @@ -1,6 +1,3 @@ # Bug component: 136048 include /services/core/java/com/android/server/input/OWNERS - -michaelwr@google.com -svv@google.com diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS index 816bc6bba639..e5d037003ac4 100644 --- a/core/java/android/hardware/soundtrigger/OWNERS +++ b/core/java/android/hardware/soundtrigger/OWNERS @@ -1 +1,2 @@ -include /core/java/android/media/soundtrigger/OWNERS +ytai@google.com +elaurent@google.com diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index 79f9e6ef2a97..dbb312720373 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -15,9 +15,6 @@ */ package android.net; -import static android.Manifest.permission.NETWORK_STACK; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -26,8 +23,7 @@ import android.content.Context; import android.os.IBinder; import android.os.ServiceManager; -import java.util.ArrayList; -import java.util.Arrays; +import com.android.net.module.util.PermissionUtils; /** * Constants and utilities for client code communicating with the network stack service. * @hide @@ -79,9 +75,14 @@ public class NetworkStack { * @param context {@link android.content.Context} for the process. * * @hide + * + * @deprecated Use {@link PermissionUtils#enforceNetworkStackPermission} instead. + * + * TODO: remove this method and let the users call to PermissionUtils directly. */ + @Deprecated public static void checkNetworkStackPermission(final @NonNull Context context) { - checkNetworkStackPermissionOr(context); + PermissionUtils.enforceNetworkStackPermission(context); } /** @@ -92,31 +93,14 @@ public class NetworkStack { * @param otherPermissions The set of permissions that could be the candidate permissions , or * empty string if none of other permissions needed. * @hide + * + * @deprecated Use {@link PermissionUtils#enforceNetworkStackPermissionOr} instead. + * + * TODO: remove this method and let the users call to PermissionUtils directly. */ + @Deprecated public static void checkNetworkStackPermissionOr(final @NonNull Context context, final @NonNull String... otherPermissions) { - ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions)); - permissions.add(NETWORK_STACK); - permissions.add(PERMISSION_MAINLINE_NETWORK_STACK); - enforceAnyPermissionOf(context, permissions.toArray(new String[0])); + PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions); } - - private static void enforceAnyPermissionOf(final @NonNull Context context, - final @NonNull String... permissions) { - if (!checkAnyPermissionOf(context, permissions)) { - throw new SecurityException("Requires one of the following permissions: " - + String.join(", ", permissions) + "."); - } - } - - private static boolean checkAnyPermissionOf(final @NonNull Context context, - final @NonNull String... permissions) { - for (String permission : permissions) { - if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { - return true; - } - } - return false; - } - } diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 49047d3a0c87..8f6510ed3ea5 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -29,6 +31,7 @@ import com.android.internal.util.Preconditions; * Class that manage network watchlist in system. * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.NETWORK_WATCHLIST_SERVICE) public class NetworkWatchlistManager { @@ -90,6 +93,7 @@ public class NetworkWatchlistManager { /** * Get Network Watchlist config file hash. */ + @Nullable public byte[] getWatchlistConfigHash() { try { return mNetworkWatchlistManager.getWatchlistConfigHash(); diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java index b172ccc4e370..f0e7da78d669 100644 --- a/core/java/android/net/UidRange.java +++ b/core/java/android/net/UidRange.java @@ -42,10 +42,6 @@ public final class UidRange implements Parcelable { stop = stopUid; } - public static UidRange createForUser(int userId) { - 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); diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index e43b0b6fa635..f90fbaf1e0fb 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -596,7 +596,8 @@ public class VpnService extends Service { } } } - mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null)); + mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null, null, + RouteInfo.RTN_UNICAST)); mConfig.updateAllowedFamilies(address); return this; } diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl index 555e9b5883e8..d91cef592d10 100644 --- a/core/java/android/net/vcn/IVcnStatusCallback.aidl +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -17,8 +17,9 @@ package android.net.vcn; /** @hide */ -interface IVcnStatusCallback { +oneway interface IVcnStatusCallback { void onEnteredSafeMode(); + void onVcnStatusChanged(int statusCode); void onGatewayConnectionError( in int[] gatewayNetworkCapabilities, int errorCode, diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index aea0ea988f50..eb8c251fec78 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.net.LinkProperties; @@ -72,8 +73,7 @@ import java.util.concurrent.Executor; public class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); - private static final Map< - VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> + private static final Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); @NonNull private final Context mContext; @@ -93,13 +93,13 @@ public class VcnManager { } /** - * Get all currently registered VcnUnderlyingNetworkPolicyListeners for testing purposes. + * Get all currently registered VcnNetworkPolicyListeners for testing purposes. * * @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull - public static Map<VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> + public static Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> getAllPolicyListeners() { return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); } @@ -161,45 +161,126 @@ public class VcnManager { } } - // TODO: make VcnUnderlyingNetworkPolicyListener @SystemApi + // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using + // the new VcnNetworkPolicyListener API /** * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components * can register to receive updates for VCN-underlying Network policies from the System Server. * * @hide */ - public interface VcnUnderlyingNetworkPolicyListener { + public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyListener {} + + /** + * Add a listener for VCN-underlying network policy updates. + * + * @param executor the Executor that will be used for invoking all calls to the specified + * Listener + * @param listener the VcnUnderlyingNetworkPolicyListener to be added + * @throws SecurityException if the caller does not have permission NETWORK_FACTORY + * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already + * registered + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void addVcnUnderlyingNetworkPolicyListener( + @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) { + addVcnNetworkPolicyListener(executor, listener); + } + + /** + * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager. + * + * <p>If the specified listener is not currently registered, this is a no-op. + * + * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed + * @hide + */ + public void removeVcnUnderlyingNetworkPolicyListener( + @NonNull VcnUnderlyingNetworkPolicyListener listener) { + removeVcnNetworkPolicyListener(listener); + } + + /** + * Queries the underlying network policy for a network with the given parameters. + * + * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy + * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network + * Provider MUST poll for the updated Network policy based on that Network's capabilities and + * properties. + * + * @param networkCapabilities the NetworkCapabilities to be used in determining the Network + * policy for this Network. + * @param linkProperties the LinkProperties to be used in determining the Network policy for + * this Network. + * @throws SecurityException if the caller does not have permission NETWORK_FACTORY + * @return the VcnUnderlyingNetworkPolicy to be used for this Network. + * @hide + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy( + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties) { + requireNonNull(networkCapabilities, "networkCapabilities must not be null"); + requireNonNull(linkProperties, "linkProperties must not be null"); + + try { + return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * VcnNetworkPolicyListener is the interface through which internal system components (e.g. + * Network Factories) can register to receive updates for VCN-underlying Network policies from + * the System Server. + * + * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks + * should register a VcnNetworkPolicyListener. VcnManager will then use this listener to notify + * the registrant when VCN Network policies change. Upon receiving this signal, the listener + * must check {@link VcnManager} for the current Network policy result for each of its Networks + * via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. + * + * @hide + */ + @SystemApi + public interface VcnNetworkPolicyListener { /** * Notifies the implementation that the VCN's underlying Network policy has changed. * - * <p>After receiving this callback, implementations MUST poll VcnManager for the updated - * VcnUnderlyingNetworkPolicy via VcnManager#getUnderlyingNetworkPolicy. + * <p>After receiving this callback, implementations should get the current {@link + * VcnNetworkPolicyResult} via {@link #applyVcnNetworkPolicy(NetworkCapabilities, + * LinkProperties)}. */ void onPolicyChanged(); } /** - * Add a listener for VCN-underlying network policy updates. + * Add a listener for VCN-underlying Network policy updates. + * + * <p>A {@link VcnNetworkPolicyListener} is eligible to begin receiving callbacks once it is + * registered. No callbacks are guaranteed upon registration. * * @param executor the Executor that will be used for invoking all calls to the specified * Listener - * @param listener the VcnUnderlyingNetworkPolicyListener to be added + * @param listener the VcnNetworkPolicyListener to be added * @throws SecurityException if the caller does not have permission NETWORK_FACTORY - * @throws IllegalArgumentException if the specified VcnUnderlyingNetworkPolicyListener is - * already registered + * @throws IllegalStateException if the specified VcnNetworkPolicyListener is already registered * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public void addVcnUnderlyingNetworkPolicyListener( - @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) { + public void addVcnNetworkPolicyListener( + @NonNull Executor executor, @NonNull VcnNetworkPolicyListener listener) { requireNonNull(executor, "executor must not be null"); requireNonNull(listener, "listener must not be null"); VcnUnderlyingNetworkPolicyListenerBinder binder = new VcnUnderlyingNetworkPolicyListenerBinder(executor, listener); if (REGISTERED_POLICY_LISTENERS.putIfAbsent(listener, binder) != null) { - throw new IllegalArgumentException( - "Attempting to add a listener that is already in use"); + throw new IllegalStateException("listener is already registered with VcnManager"); } try { @@ -211,15 +292,15 @@ public class VcnManager { } /** - * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager. + * Remove the specified VcnNetworkPolicyListener from VcnManager. * * <p>If the specified listener is not currently registered, this is a no-op. * - * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed + * @param listener the VcnNetworkPolicyListener that will be removed * @hide */ - public void removeVcnUnderlyingNetworkPolicyListener( - @NonNull VcnUnderlyingNetworkPolicyListener listener) { + @SystemApi + public void removeVcnNetworkPolicyListener(@NonNull VcnNetworkPolicyListener listener) { requireNonNull(listener, "listener must not be null"); VcnUnderlyingNetworkPolicyListenerBinder binder = @@ -236,39 +317,88 @@ public class VcnManager { } /** - * Queries the underlying network policy for a network with the given parameters. + * Applies the network policy for a {@link android.net.Network} with the given parameters. * * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy - * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network - * Provider MUST poll for the updated Network policy based on that Network's capabilities and - * properties. + * may have changed via {@link VcnNetworkPolicyListener#onPolicyChanged()}, a Network Provider + * MUST poll for the updated Network policy based on that Network's capabilities and properties. * * @param networkCapabilities the NetworkCapabilities to be used in determining the Network - * policy for this Network. - * @param linkProperties the LinkProperties to be used in determining the Network policy for - * this Network. + * policy result for this Network. + * @param linkProperties the LinkProperties to be used in determining the Network policy result + * for this Network. * @throws SecurityException if the caller does not have permission NETWORK_FACTORY - * @return the VcnUnderlyingNetworkPolicy to be used for this Network. + * @return the {@link VcnNetworkPolicyResult} to be used for this Network. * @hide */ @NonNull + @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy( + public VcnNetworkPolicyResult applyVcnNetworkPolicy( @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties) { requireNonNull(networkCapabilities, "networkCapabilities must not be null"); requireNonNull(linkProperties, "linkProperties must not be null"); - try { - return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final VcnUnderlyingNetworkPolicy policy = + getUnderlyingNetworkPolicy(networkCapabilities, linkProperties); + return new VcnNetworkPolicyResult( + policy.isTeardownRequested(), policy.getMergedNetworkCapabilities()); } /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ + VCN_STATUS_CODE_NOT_CONFIGURED, + VCN_STATUS_CODE_INACTIVE, + VCN_STATUS_CODE_ACTIVE, + VCN_STATUS_CODE_SAFE_MODE + }) + public @interface VcnStatusCode {} + + /** + * Value indicating that the VCN for the subscription group is not configured, or that the + * callback is not privileged for the subscription group. + * + * @hide + */ + public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; + + /** + * Value indicating that the VCN for the subscription group is inactive. + * + * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the + * provisioning package is not privileged. + * + * @hide + */ + public static final int VCN_STATUS_CODE_INACTIVE = 1; + + /** + * Value indicating that the VCN for the subscription group is active. + * + * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning + * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered + * active while it is connecting, fully connected, and disconnecting. + * + * @hide + */ + public static final int VCN_STATUS_CODE_ACTIVE = 2; + + /** + * Value indicating that the VCN for the subscription group is in Safe Mode. + * + * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to + * establish a connection within a system-determined timeout (while underlying networks were + * available). + * + * @hide + */ + public static final int VCN_STATUS_CODE_SAFE_MODE = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ VCN_ERROR_CODE_INTERNAL_ERROR, VCN_ERROR_CODE_CONFIG_ERROR, VCN_ERROR_CODE_NETWORK_ERROR @@ -323,8 +453,18 @@ public class VcnManager { * * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. + * + * @hide */ - public abstract void onEnteredSafeMode(); + public void onEnteredSafeMode() {} + + /** + * Invoked when status of the VCN for this callback's subscription group changes. + * + * @param statusCode the code for the status change encountered by this {@link + * VcnStatusCallback}'s subscription group. + */ + public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode); /** * Invoked when a VCN Gateway Connection corresponding to this callback's subscription @@ -356,6 +496,11 @@ public class VcnManager { * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier * privileges for the specified subscription at the time of invocation. * + * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the + * current status for the specified subscription group's VCN. If the registrant is not + * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be + * returned. + * * @param subscriptionGroup The subscription group to match for callbacks * @param executor The {@link Executor} to be used for invoking callbacks * @param callback The VcnStatusCallback to be registered @@ -415,18 +560,17 @@ public class VcnManager { } /** - * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System - * Server. + * Binder wrapper for added VcnNetworkPolicyListeners to receive signals from System Server. * * @hide */ private static class VcnUnderlyingNetworkPolicyListenerBinder extends IVcnUnderlyingNetworkPolicyListener.Stub { @NonNull private final Executor mExecutor; - @NonNull private final VcnUnderlyingNetworkPolicyListener mListener; + @NonNull private final VcnNetworkPolicyListener mListener; private VcnUnderlyingNetworkPolicyListenerBinder( - Executor executor, VcnUnderlyingNetworkPolicyListener listener) { + Executor executor, VcnNetworkPolicyListener listener) { mExecutor = executor; mListener = listener; } @@ -460,6 +604,12 @@ public class VcnManager { () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); } + @Override + public void onVcnStatusChanged(@VcnStatusCode int statusCode) { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode))); + } + // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' @Override public void onGatewayConnectionError( diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl b/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl new file mode 100644 index 000000000000..3f13abe869da --- /dev/null +++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl @@ -0,0 +1,20 @@ +/* + * 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.net.vcn; + +/** @hide */ +parcelable VcnNetworkPolicyResult; diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/core/java/android/net/vcn/VcnNetworkPolicyResult.java new file mode 100644 index 000000000000..5e938200639c --- /dev/null +++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.java @@ -0,0 +1,114 @@ +/* + * 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.net.vcn; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.NetworkCapabilities; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * VcnNetworkPolicyResult represents the Network policy result for a Network transport applying its + * VCN policy via {@link VcnManager#applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. + * + * <p>Bearers that are bringing up networks capable of acting as a VCN's underlying network should + * query for Network policy results upon any capability changes (e.g. changing of TRUSTED bit), and + * when prompted by VcnManagementService via {@link VcnManager.VcnNetworkPolicyListener}. + * + * @hide + */ +@SystemApi +public final class VcnNetworkPolicyResult implements Parcelable { + private final boolean mIsTearDownRequested; + private final NetworkCapabilities mNetworkCapabilities; + + /** + * Constructs a VcnNetworkPolicyResult with the specified parameters. + * + * @hide + */ + public VcnNetworkPolicyResult( + boolean isTearDownRequested, @NonNull NetworkCapabilities networkCapabilities) { + Objects.requireNonNull(networkCapabilities, "networkCapabilities must be non-null"); + + mIsTearDownRequested = isTearDownRequested; + mNetworkCapabilities = networkCapabilities; + } + + /** + * Returns whether this VCN policy result requires that the underlying Network should be torn + * down. + * + * <p>Upon querying for the current Network policy result, the bearer must check this method, + * and MUST tear down the corresponding Network if it returns true. + */ + public boolean isTeardownRequested() { + return mIsTearDownRequested; + } + + /** + * Returns the NetworkCapabilities that the bearer should be using for the corresponding + * Network. + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + @Override + public int hashCode() { + return Objects.hash(mIsTearDownRequested, mNetworkCapabilities); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof VcnNetworkPolicyResult)) return false; + final VcnNetworkPolicyResult that = (VcnNetworkPolicyResult) o; + + return mIsTearDownRequested == that.mIsTearDownRequested + && mNetworkCapabilities.equals(that.mNetworkCapabilities); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mIsTearDownRequested); + dest.writeParcelable(mNetworkCapabilities, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator<VcnNetworkPolicyResult> CREATOR = + new Creator<VcnNetworkPolicyResult>() { + public VcnNetworkPolicyResult createFromParcel(Parcel in) { + return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null)); + } + + public VcnNetworkPolicyResult[] newArray(int size) { + return new VcnNetworkPolicyResult[size]; + } + }; +} diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java index dd7c86d87ff2..b47d5642419e 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java @@ -33,8 +33,7 @@ import java.util.Objects; * @hide */ public final class VcnUnderlyingNetworkPolicy implements Parcelable { - private final boolean mIsTearDownRequested; - private final NetworkCapabilities mMergedNetworkCapabilities; + private final VcnNetworkPolicyResult mVcnNetworkPolicyResult; /** * Constructs a VcnUnderlyingNetworkPolicy with the specified parameters. @@ -46,8 +45,13 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { Objects.requireNonNull( mergedNetworkCapabilities, "mergedNetworkCapabilities must be nonnull"); - mIsTearDownRequested = isTearDownRequested; - mMergedNetworkCapabilities = mergedNetworkCapabilities; + mVcnNetworkPolicyResult = + new VcnNetworkPolicyResult(isTearDownRequested, mergedNetworkCapabilities); + } + + private VcnUnderlyingNetworkPolicy(@NonNull VcnNetworkPolicyResult vcnNetworkPolicyResult) { + this.mVcnNetworkPolicyResult = + Objects.requireNonNull(vcnNetworkPolicyResult, "vcnNetworkPolicyResult"); } /** @@ -55,7 +59,7 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { * be torn down. */ public boolean isTeardownRequested() { - return mIsTearDownRequested; + return mVcnNetworkPolicyResult.isTeardownRequested(); } /** @@ -64,12 +68,12 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { */ @NonNull public NetworkCapabilities getMergedNetworkCapabilities() { - return mMergedNetworkCapabilities; + return mVcnNetworkPolicyResult.getNetworkCapabilities(); } @Override public int hashCode() { - return Objects.hash(mIsTearDownRequested, mMergedNetworkCapabilities); + return Objects.hash(mVcnNetworkPolicyResult); } @Override @@ -78,8 +82,7 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { if (!(o instanceof VcnUnderlyingNetworkPolicy)) return false; final VcnUnderlyingNetworkPolicy that = (VcnUnderlyingNetworkPolicy) o; - return mIsTearDownRequested == that.mIsTearDownRequested - && mMergedNetworkCapabilities.equals(that.mMergedNetworkCapabilities); + return mVcnNetworkPolicyResult.equals(that.mVcnNetworkPolicyResult); } /** {@inheritDoc} */ @@ -91,16 +94,14 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { /** {@inheritDoc} */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeBoolean(mIsTearDownRequested); - dest.writeParcelable(mMergedNetworkCapabilities, flags); + dest.writeParcelable(mVcnNetworkPolicyResult, flags); } /** Implement the Parcelable interface */ public static final @NonNull Creator<VcnUnderlyingNetworkPolicy> CREATOR = new Creator<VcnUnderlyingNetworkPolicy>() { public VcnUnderlyingNetworkPolicy createFromParcel(Parcel in) { - return new VcnUnderlyingNetworkPolicy( - in.readBoolean(), in.readParcelable(null)); + return new VcnUnderlyingNetworkPolicy(in.readParcelable(null)); } public VcnUnderlyingNetworkPolicy[] newArray(int size) { diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index c6efaace76d7..2b6f336848c3 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -47,6 +47,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_SYSTEM_SERVICES, POWER_COMPONENT_SENSORS, POWER_COMPONENT_GNSS, + POWER_COMPONENT_WIFI, POWER_COMPONENT_WAKELOCK, POWER_COMPONENT_SCREEN, POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, @@ -66,6 +67,7 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_MOBILE_RADIO = 8; public static final int POWER_COMPONENT_SENSORS = 9; public static final int POWER_COMPONENT_GNSS = 10; + public static final int POWER_COMPONENT_WIFI = 11; public static final int POWER_COMPONENT_WAKELOCK = 12; public static final int POWER_COMPONENT_SCREEN = 13; // Power that is re-attributed to other battery consumers. For example, for System Server @@ -94,6 +96,7 @@ public abstract class BatteryConsumer { TIME_COMPONENT_MOBILE_RADIO, TIME_COMPONENT_SENSORS, TIME_COMPONENT_GNSS, + TIME_COMPONENT_WIFI, TIME_COMPONENT_WAKELOCK, TIME_COMPONENT_SCREEN, }) @@ -112,6 +115,7 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_MOBILE_RADIO = 8; public static final int TIME_COMPONENT_SENSORS = 9; public static final int TIME_COMPONENT_GNSS = 10; + public static final int TIME_COMPONENT_WIFI = 11; public static final int TIME_COMPONENT_WAKELOCK = 12; public static final int TIME_COMPONENT_SCREEN = 13; diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 305c686f8657..a435ac12d33c 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.WorkerThread; import android.app.ActivityManager; import android.content.Context; import android.util.Log; @@ -41,7 +42,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; -/** Class that provides a privileged API to capture and consume bugreports. */ +/** + * Class that provides a privileged API to capture and consume bugreports. + * + * <p>This class may only be used by apps that currently have carrier privileges (see {@link + * android.telephony.TelephonyManager#hasCarrierPrivileges}) on an active SIM or priv-apps + * explicitly allowed by the device manufacturer. + * + * <p>Only one bugreport can be generated by the system at a time. + */ @SystemService(Context.BUGREPORT_SERVICE) public final class BugreportManager { @@ -56,7 +65,12 @@ public final class BugreportManager { mBinder = binder; } - /** An interface describing the callback for bugreport progress and status. */ + /** + * An interface describing the callback for bugreport progress and status. + * + * <p>In general, callers can expect to receive {@link #onProgress} calls as the bugreport + * progresses, followed by a terminal call to either {@link #onFinished} or {@link #onError}. + */ public abstract static class BugreportCallback { /** * Possible error codes taking a bugreport can encounter. @@ -75,15 +89,18 @@ public final class BugreportManager { }) public @interface BugreportErrorCode {} - /** The input options were invalid */ + /** + * The input options were invalid. For example, the destination file the app provided could + * not be written by the system. + */ public static final int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; - /** A runtime error occurred */ + /** A runtime error occurred. */ public static final int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; - /** User denied consent to share the bugreport */ + /** User denied consent to share the bugreport. */ public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; @@ -149,6 +166,7 @@ public final class BugreportManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.DUMP) + @WorkerThread public void startBugreport( @NonNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @@ -222,6 +240,7 @@ public final class BugreportManager { * @param callback callback for progress and status updates. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @WorkerThread public void startConnectivityBugreport( @NonNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @@ -247,6 +266,7 @@ public final class BugreportManager { * @throws SecurityException if trying to cancel another app's bugreport in progress */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @WorkerThread public void cancelBugreport() { try { mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName()); diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 93c1690e3813..43184ea4b9a9 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -26,6 +26,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.KeyguardManager; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; @@ -631,10 +632,15 @@ public class RecoverySystem { /** * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup - * and ready to apply the OTA. This API is expected to handle requests from multiple clients - * simultaneously, e.g. from ota and mainline. + * and ready to apply the OTA. <p> * - * <p> The behavior of multi-client Resume on Reboot works as follows + * <p> If the device doesn't setup a lock screen, i.e. by checking + * {@link KeyguardManager#isKeyguardSecure()}, this API call will fail and throw an exception. + * Callers are expected to use {@link PowerManager#reboot(String)} directly without going + * through the RoR flow. <p> + * + * <p> This API is expected to handle requests from multiple clients simultaneously, e.g. + * from ota and mainline. The behavior of multi-client Resume on Reboot works as follows * <li> Each client should call this function to prepare for Resume on Reboot before calling * {@link #rebootAndApply(Context, String, boolean)} </li> * <li> One client cannot clear the Resume on Reboot preparation of another client. </li> @@ -658,6 +664,13 @@ public class RecoverySystem { if (updateToken == null) { throw new NullPointerException("updateToken == null"); } + + KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); + if (keyguardManager == null || !keyguardManager.isDeviceSecure()) { + throw new IOException("Failed to request LSKF because the device doesn't have a" + + " lock screen. "); + } + RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); if (!rs.requestLskf(context.getPackageName(), intentSender)) { throw new IOException("preparation for update failed"); diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 1bdc82a82c6c..97e03e9d0d94 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -38,6 +38,23 @@ "include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest" } ] + }, + { + "file_patterns": ["BatteryStats.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["BatteryStats.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] } ], "postsubmit": [ diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index bb40d905d481..dfa0c396485d 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -16,9 +16,13 @@ package android.os; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Contains power consumption data attributed to a specific UID. * @@ -26,9 +30,37 @@ import android.annotation.Nullable; */ public final class UidBatteryConsumer extends BatteryConsumer implements Parcelable { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + STATE_FOREGROUND, + STATE_BACKGROUND + }) + public @interface State { + } + + /** + * The state of an application when it is either running a foreground (top) activity + * or a foreground service. + */ + public static final int STATE_FOREGROUND = 0; + + /** + * The state of an application when it is running in the background, including the following + * states: + * + * {@link android.app.ActivityManager#PROCESS_STATE_IMPORTANT_BACKGROUND}, + * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND}, + * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP}, + * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE}, + * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}. + */ + public static final int STATE_BACKGROUND = 1; + private final int mUid; @Nullable private final String mPackageWithHighestDrain; + private final long mTimeInForegroundMs; + private final long mTimeInBackgroundMs; public int getUid() { return mUid; @@ -39,16 +71,33 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela return mPackageWithHighestDrain; } + /** + * Returns the amount of time in milliseconds this UID spent in the specified state. + */ + public long getTimeInStateMs(@State int state) { + switch (state) { + case STATE_BACKGROUND: + return mTimeInBackgroundMs; + case STATE_FOREGROUND: + return mTimeInForegroundMs; + } + return 0; + } + private UidBatteryConsumer(@NonNull Builder builder) { super(builder.mPowerComponentsBuilder.build()); mUid = builder.mUid; mPackageWithHighestDrain = builder.mPackageWithHighestDrain; + mTimeInForegroundMs = builder.mTimeInForegroundMs; + mTimeInBackgroundMs = builder.mTimeInBackgroundMs; } private UidBatteryConsumer(@NonNull Parcel source) { super(new PowerComponents(source)); mUid = source.readInt(); mPackageWithHighestDrain = source.readString(); + mTimeInForegroundMs = source.readLong(); + mTimeInBackgroundMs = source.readLong(); } /** @@ -59,6 +108,8 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela super.writeToParcel(dest, flags); dest.writeInt(mUid); dest.writeString(mPackageWithHighestDrain); + dest.writeLong(mTimeInForegroundMs); + dest.writeLong(mTimeInBackgroundMs); } @NonNull @@ -84,6 +135,8 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; private String mPackageWithHighestDrain; + public long mTimeInForegroundMs; + public long mTimeInBackgroundMs; private boolean mExcludeFromBatteryUsageStats; public Builder(int customPowerComponentCount, int customTimeComponentCount, @@ -113,6 +166,25 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela } /** + * Sets the duration, in milliseconds, that this UID was active in a particular state, + * such as foreground or background. + */ + @NonNull + public Builder setTimeInStateMs(@State int state, long timeInStateMs) { + switch (state) { + case STATE_FOREGROUND: + mTimeInForegroundMs = timeInStateMs; + break; + case STATE_BACKGROUND: + mTimeInBackgroundMs = timeInStateMs; + break; + default: + throw new IllegalArgumentException("Unsupported state: " + state); + } + return this; + } + + /** * Marks the UidBatteryConsumer for exclusion from the result set. */ public Builder excludeFromBatteryUsageStats() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 8bdfd3d3d627..682754e66904 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1688,6 +1688,7 @@ public class UserManager { * @return Whether guest user is always ephemeral * @hide */ + @TestApi public static boolean isGuestUserEphemeral() { return Resources.getSystem() .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral); @@ -1802,6 +1803,20 @@ public class UserManager { } /** + * @return the user type of the context user. + * @hide + */ + @TestApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + @UserHandleAware + public @NonNull String getUserType() { + UserInfo userInfo = getUserInfo(mUserId); + return userInfo == null ? "" : userInfo.userType; + } + + /** * Returns the user name of the context user. This call is only available to applications on * the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code * android.permission.GET_ACCOUNTS_PRIVILEGED} permissions. @@ -1809,7 +1824,8 @@ public class UserManager { * @return the user name */ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional = true) + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, + android.Manifest.permission.CREATE_USERS}, conditional = true) @UserHandleAware public @NonNull String getUserName() { if (UserHandle.myUserId() == mUserId) { @@ -2792,6 +2808,7 @@ public class UserManager { */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) + @TestApi public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags) { try { @@ -2828,6 +2845,7 @@ public class UserManager { * @throws UserOperationException if the user could not be created. * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public @NonNull UserInfo preCreateUser(@NonNull String userType) @@ -2976,10 +2994,11 @@ public class UserManager { * * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { + public @Nullable UserInfo createProfileForUser(@Nullable String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { try { return mService.createProfileForUserWithThrow(name, userType, flags, userId, disallowedPackages); @@ -3022,9 +3041,10 @@ public class UserManager { * * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createRestrictedProfile(String name) { + public @Nullable UserInfo createRestrictedProfile(@Nullable String name) { try { UserHandle parentUserHandle = Process.myUserHandle(); UserInfo user = mService.createRestrictedProfileWithThrow(name, @@ -3248,10 +3268,11 @@ public class UserManager { /** * Return the number of users currently created on the device. - * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS} - * permission.</p> */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public int getUserCount() { List<UserInfo> users = getUsers(); return users != null ? users.size() : 1; @@ -3274,7 +3295,10 @@ public class UserManager { * @hide */ @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public List<UserInfo> getUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ false, /* excludePreCreated= */ true); @@ -3292,7 +3316,10 @@ public class UserManager { * @return the list of users that were created. * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserInfo> getAliveUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ true, /* excludePreCreated= */ true); @@ -3306,7 +3333,10 @@ public class UserManager { */ @Deprecated @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserInfo> getUsers(boolean excludeDying) { return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3317,8 +3347,12 @@ public class UserManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) { try { return mService.getUsers(excludePartial, excludeDying, excludePreCreated); @@ -3335,7 +3369,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) { List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3354,7 +3391,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public long[] getSerialNumbersOfUsers(boolean excludeDying) { List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3678,7 +3718,10 @@ public class UserManager { * @hide */ @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS + }) public UserInfo getProfileParent(@UserIdInt int userId) { try { return mService.getProfileParent(userId); @@ -3697,7 +3740,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS + }) public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) { UserInfo info = getProfileParent(user.getIdentifier()); diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 06203ff15094..9ffc5aa0022c 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -426,7 +426,7 @@ public class ZygoteProcess { // avoid writing a partial response to the zygote. for (String arg : args) { // Making two indexOf calls here is faster than running a manually fused loop due - // to the fact that indexOf is a optimized intrinsic. + // to the fact that indexOf is an optimized intrinsic. if (arg.indexOf('\n') >= 0) { throw new ZygoteStartFailedEx("Embedded newlines not allowed"); } else if (arg.indexOf('\r') >= 0) { diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index cec6a1fb271d..592e98abae63 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -241,10 +241,12 @@ public final class IncrementalManager { } /** - * Checks if device supports V2 calls (e.g. PerUid). + * 0 - IncFs is disabled. + * 1 - IncFs v1, core features, no PerUid support. Optional in R. + * 2 - IncFs v2, PerUid support, fs-verity support. Required in S. */ - public static boolean isV2Available() { - return nativeIsV2Available(); + public static int getVersion() { + return nativeIsEnabled() ? nativeIsV2Available() ? 2 : 1 : 0; } /** diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java index 11d26cab14b3..d8c22fd94ce9 100644 --- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java +++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java @@ -24,7 +24,7 @@ import android.content.Context; * instance. * * @see Context#getSystemService(String) - * @see Context#isUiContext(Context) + * @see Context#isUiContext * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse() */ public final class IncorrectContextUseViolation extends Violation { diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index ff01011bd19b..bae36b299247 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -485,10 +485,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * * @param packageName the app for which to get allowlisted permissions @@ -502,7 +498,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ @@ -549,10 +544,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * <p> * You need to specify the allowlists for which to set the allowlisted permissions which will @@ -570,7 +561,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ @@ -613,10 +603,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * <p> * You need to specify the allowlists for which to set the allowlisted permissions which will @@ -634,7 +620,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index e134c29520b2..6e89faf9c2ed 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -157,6 +157,14 @@ public final class DeviceConfig { public static final String NAMESPACE_BLUETOOTH = "bluetooth"; /** + * Namespace for features relating to clipboard. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_CLIPBOARD = "clipboard"; + + /** * Namespace for all networking connectivity related features. * * @hide @@ -275,6 +283,14 @@ public final class DeviceConfig { public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot"; /** + * Namespace for features related to Reboot Readiness detection. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness"; + + /** * Namespace for Rollback flags that are applied immediately. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ff10b0caab89..09d0af11a39f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -417,6 +417,22 @@ public final class Settings { "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; /** + * Activity Action: Show settings to allow configuration of + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". + * <p> + * Output: When a package data uri is passed as input, the activity result is set to + * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise, + * the result is set to {@link android.app.Activity#RESULT_CANCELED}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = + "android.settings.REQUEST_SCHEDULE_EXACT_ALARM"; + + /** * Activity Action: Show settings to allow configuration of cross-profile access for apps * * Input: Optionally, the Intent's data URI can specify the application package name to diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java index 074d5f167ec3..030b86339822 100644 --- a/core/java/android/provider/SimPhonebookContract.java +++ b/core/java/android/provider/SimPhonebookContract.java @@ -44,8 +44,11 @@ import java.util.Objects; * The contract between the provider of contact records on the device's SIM cards and applications. * Contains definitions of the supported URIs and columns. * - * <p>This content provider does not support any of the QUERY_ARG_SQL* bundle arguments. An - * IllegalArgumentException will be thrown if these are included. + * <h3>Permissions</h3> + * <p> + * Querying this provider requires {@link android.Manifest.permission#READ_CONTACTS} and writing + * to this provider requires {@link android.Manifest.permission#WRITE_CONTACTS} + * </p> */ public final class SimPhonebookContract { @@ -85,7 +88,73 @@ public final class SimPhonebookContract { } } - /** Constants for the contact records on a SIM card. */ + /** + * Constants for the contact records on a SIM card. + * + * <h3 id="simrecords-data">Data</h3> + * <p> + * Data is stored in a specific elementary file on a specific SIM card and these are isolated + * from each other. SIM cards are identified by their subscription ID. SIM cards may not support + * all or even any of the elementary file types. A SIM will have constraints on + * the values of the data that can be stored in each elementary file. The available SIMs, + * their supported elementary file types and the constraints on the data can be discovered by + * querying {@link ElementaryFiles#CONTENT_URI}. Each elementary file has a fixed capacity + * for the number of records that may be stored. This can be determined from the value + * of the {@link ElementaryFiles#MAX_RECORDS} column. + * </p> + * <p> + * The {@link SimRecords#PHONE_NUMBER} column can only contain dialable characters and this + * applies regardless of the SIM that is being used. See + * {@link android.telephony.PhoneNumberUtils#isDialable(char)} for more details. Additionally + * the phone number can contain at most {@link ElementaryFiles#PHONE_NUMBER_MAX_LENGTH} + * characters. The {@link SimRecords#NAME} column can contain at most + * {@link ElementaryFiles#NAME_MAX_LENGTH} bytes when it is encoded for storage on the SIM. + * Encoding is done internally and so the name should be provided unencoded but the number of + * bytes required to encode it will vary depending on the characters it contains. This length + * can be determined by calling + * {@link SimRecords#getEncodedNameLength(ContentResolver, String)}. + * </p> + * <h3>Operations </h3> + * <dl> + * <dd><b>Insert</b></dd> + * <p> + * Only {@link ElementaryFiles#EF_ADN} supports inserts. {@link SimRecords#PHONE_NUMBER} + * is a required column. If the value provided for this column is missing, null, empty + * or violates the requirements discussed in the <a href="#simrecords-data">Data</a> + * section above an {@link IllegalArgumentException} will be thrown. The + * {@link SimRecords#NAME} column may be omitted but if provided and it violates any of + * the requirements discussed in the <a href="#simrecords-data">Data</a> section above + * an {@link IllegalArgumentException} will be thrown. + * </p> + * <p> + * If an insert is not possible because the elementary file is full then an + * {@link IllegalStateException} will be thrown. + * </p> + * <dd><b>Update</b></dd> + * <p> + * Updates can only be performed for individual records on {@link ElementaryFiles#EF_ADN}. + * A specific record is referenced via the Uri returned by + * {@link SimRecords#getItemUri(int, int, int)}. Updates have the same constraints and + * behavior for the {@link SimRecords#PHONE_NUMBER} and {@link SimRecords#NAME} as insert. + * However, in the case of update the {@link SimRecords#PHONE_NUMBER} may be omitted as + * the existing record will already have a valid value. + * </p> + * <dd><b>Delete</b></dd> + * <p> + * Delete may only be performed for individual records on {@link ElementaryFiles#EF_ADN}. + * Deleting records will free up space for use by future inserts. + * </p> + * <dd><b>Query</b></dd> + * <p> + * All the records stored on a specific elementary file can be read via a Uri returned by + * {@link SimRecords#getContentUri(int, int)}. This query always returns all records; there + * is no support for filtering via a selection. An individual record can be queried via a Uri + * returned by {@link SimRecords#getItemUri(int, int, int)}. Queries will throw an + * {@link IllegalArgumentException} when the SIM with the subscription ID or the elementary file + * type are invalid or unavailable. + * </p> + * </dl> + */ public static final class SimRecords { /** @@ -197,8 +266,8 @@ public final class SimPhonebookContract { * be discovered by querying {@link ElementaryFiles#CONTENT_URI}. * * <p>If a SIM with the provided subscription ID does not exist or the SIM with the provided - * subscription ID doesn't support the specified entity file then queries will return - * and empty cursor and inserts will throw an {@link IllegalArgumentException} + * subscription ID doesn't support the specified entity file then all operations will + * throw an {@link IllegalArgumentException}. * * @param subscriptionId the subscriptionId of the SIM card that this Uri will reference * @param efType the elementary file on the SIM that this Uri will reference @@ -233,6 +302,9 @@ public final class SimPhonebookContract { * must be greater than 0. If there is no record with this record * number in the specified entity file then it will be treated as a * non-existent record. + * @see ElementaryFiles#SUBSCRIPTION_ID + * @see ElementaryFiles#EF_TYPE + * @see #RECORD_NUMBER */ @NonNull public static Uri getItemUri( @@ -287,7 +359,28 @@ public final class SimPhonebookContract { } - /** Constants for metadata about the elementary files of the SIM cards in the phone. */ + /** + * Constants for metadata about the elementary files of the SIM cards in the phone. + * + * <h3>Operations </h3> + * <dl> + * <dd><b>Insert</b></dd> + * <p>Insert is not supported for the Uris defined in this class.</p> + * <dd><b>Update</b></dd> + * <p>Update is not supported for the Uris defined in this class.</p> + * <dd><b>Delete</b></dd> + * <p>Delete is not supported for the Uris defined in this class.</p> + * <dd><b>Query</b></dd> + * <p> + * The elementary files for all the inserted SIMs can be read via + * {@link ElementaryFiles#CONTENT_URI}. Unsupported elementary files are omitted from the + * results. This Uri always returns all supported elementary files for all available SIMs; it + * does not support filtering via a selection. A specific elementary file can be queried + * via a Uri returned by {@link ElementaryFiles#getItemUri(int, int)}. If the elementary file + * referenced by this Uri is unsupported by the SIM then the query will return an empty cursor. + * </p> + * </dl> + */ public static final class ElementaryFiles { /** {@link SubscriptionInfo#getSimSlotIndex()} of the SIM for this row. */ diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index ffb4a6eeb1ab..3c355d4b6f45 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -270,13 +270,13 @@ public class TelephonyRegistryManager { /** * Notify call state changed on certain subscription. * - * @param subId for which call state changed. * @param slotIndex for which call state changed. Can be derived from subId except when subId is * invalid. + * @param subId for which call state changed. * @param state latest call state. e.g, offhook, ringing * @param incomingNumber incoming phone number. */ - public void notifyCallStateChanged(int subId, int slotIndex, @CallState int state, + public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state, @Nullable String incomingNumber) { try { sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber); @@ -329,12 +329,12 @@ public class TelephonyRegistryManager { /** * Notify {@link ServiceState} update on certain subscription. * - * @param subId for which the service state changed. * @param slotIndex for which the service state changed. Can be derived from subId except * subId is invalid. + * @param subId for which the service state changed. * @param state service state e.g, in service, out of service or roaming status. */ - public void notifyServiceStateChanged(int subId, int slotIndex, @NonNull ServiceState state) { + public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) { try { sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state); } catch (RemoteException ex) { @@ -345,12 +345,12 @@ public class TelephonyRegistryManager { /** * Notify {@link SignalStrength} update on certain subscription. * - * @param subId for which the signalstrength changed. * @param slotIndex for which the signalstrength changed. Can be derived from subId except when * subId is invalid. + * @param subId for which the signalstrength changed. * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()} */ - public void notifySignalStrengthChanged(int subId, int slotIndex, + public void notifySignalStrengthChanged(int slotIndex, int subId, @NonNull SignalStrength signalStrength) { try { sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength); @@ -363,13 +363,13 @@ public class TelephonyRegistryManager { * Notify changes to the message-waiting indicator on certain subscription. e.g, The status bar * uses message waiting indicator to determine when to display the voicemail icon. * - * @param subId for which message waiting indicator changed. * @param slotIndex for which message waiting indicator changed. Can be derived from subId * except when subId is invalid. + * @param subId for which message waiting indicator changed. * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false} * otherwise. */ - public void notifyMessageWaitingChanged(int subId, int slotIndex, boolean msgWaitingInd) { + public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) { try { sRegistry.notifyMessageWaitingChangedForPhoneId(slotIndex, subId, msgWaitingInd); } catch (RemoteException ex) { @@ -410,9 +410,9 @@ public class TelephonyRegistryManager { /** * Notify changes to default (Internet) data connection state on certain subscription. * - * @param subId for which data connection state changed. * @param slotIndex for which data connections state changed. Can be derived from subId except * when subId is invalid. + * @param subId for which data connection state changed. * @param preciseState the PreciseDataConnectionState * * @see PreciseDataConnectionState @@ -431,13 +431,13 @@ public class TelephonyRegistryManager { /** * Notify {@link CallQuality} change on certain subscription. * - * @param subId for which call quality state changed. * @param slotIndex for which call quality state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which call quality state changed. * @param callQuality Information about call quality e.g, call quality level * @param networkType associated with this data connection. e.g, LTE */ - public void notifyCallQualityChanged(int subId, int slotIndex, @NonNull CallQuality callQuality, + public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality, @NetworkType int networkType) { try { sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType); @@ -449,11 +449,11 @@ public class TelephonyRegistryManager { /** * Notify emergency number list changed on certain subscription. * - * @param subId for which emergency number list changed. * @param slotIndex for which emergency number list changed. Can be derived from subId except * when subId is invalid. + * @param subId for which emergency number list changed. */ - public void notifyEmergencyNumberList(int subId, int slotIndex) { + public void notifyEmergencyNumberList( int slotIndex, int subId) { try { sRegistry.notifyEmergencyNumberList(slotIndex, subId); } catch (RemoteException ex) { @@ -494,13 +494,13 @@ public class TelephonyRegistryManager { /** * Notify radio power state changed on certain subscription. * - * @param subId for which radio power state changed. * @param slotIndex for which radio power state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which radio power state changed. * @param radioPowerState the current modem radio state. */ - public void notifyRadioPowerStateChanged(int subId, int slotIndex, - @RadioPowerState int radioPowerState) { + public void notifyRadioPowerStateChanged(int slotIndex, int subId, + @RadioPowerState int radioPowerState) { try { sRegistry.notifyRadioPowerStateChanged(slotIndex, subId, radioPowerState); } catch (RemoteException ex) { @@ -538,13 +538,13 @@ public class TelephonyRegistryManager { * Notify data activation state changed on certain subscription. * @see TelephonyManager#getDataActivationState() * - * @param subId for which data activation state changed. * @param slotIndex for which data activation state changed. Can be derived from subId except * when subId is invalid. + * @param subId for which data activation state changed. * @param activationState sim activation state e.g, activated. */ - public void notifyDataActivationStateChanged(int subId, int slotIndex, - @SimActivationState int activationState) { + public void notifyDataActivationStateChanged(int slotIndex, int subId, + @SimActivationState int activationState) { try { sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId, SIM_ACTIVATION_TYPE_DATA, activationState); @@ -557,13 +557,13 @@ public class TelephonyRegistryManager { * Notify voice activation state changed on certain subscription. * @see TelephonyManager#getVoiceActivationState() * - * @param subId for which voice activation state changed. * @param slotIndex for which voice activation state changed. Can be derived from subId except * subId is invalid. + * @param subId for which voice activation state changed. * @param activationState sim activation state e.g, activated. */ - public void notifyVoiceActivationStateChanged(int subId, int slotIndex, - @SimActivationState int activationState) { + public void notifyVoiceActivationStateChanged(int slotIndex, int subId, + @SimActivationState int activationState) { try { sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId, SIM_ACTIVATION_TYPE_VOICE, activationState); @@ -576,9 +576,9 @@ public class TelephonyRegistryManager { * Notify User mobile data state changed on certain subscription. e.g, mobile data is enabled * or disabled. * - * @param subId for which mobile data state has changed. * @param slotIndex for which mobile data state has changed. Can be derived from subId except * when subId is invalid. + * @param subId for which mobile data state has changed. * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise. */ public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) { @@ -599,7 +599,7 @@ public class TelephonyRegistryManager { * @param telephonyDisplayInfo The display info. */ public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId, - @NonNull TelephonyDisplayInfo telephonyDisplayInfo) { + @NonNull TelephonyDisplayInfo telephonyDisplayInfo) { try { sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, telephonyDisplayInfo); } catch (RemoteException ex) { @@ -640,14 +640,14 @@ public class TelephonyRegistryManager { * Notify precise call state changed on certain subscription, including foreground, background * and ringcall states. * - * @param subId for which precise call state changed. * @param slotIndex for which precise call state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which precise call state changed. * @param ringCallPreciseState ringCall state. * @param foregroundCallPreciseState foreground call state. * @param backgroundCallPreciseState background call state. */ - public void notifyPreciseCallState(int subId, int slotIndex, + public void notifyPreciseCallState(int slotIndex, int subId, @PreciseCallStates int ringCallPreciseState, @PreciseCallStates int foregroundCallPreciseState, @PreciseCallStates int backgroundCallPreciseState) { @@ -790,9 +790,10 @@ public class TelephonyRegistryManager { * @param reason Reason for data enabled/disabled. See {@code REASON_*} in * {@link TelephonyManager}. */ - public void notifyDataEnabled(boolean enabled, @TelephonyManager.DataEnabledReason int reason) { + public void notifyDataEnabled(int slotIndex, int subId, boolean enabled, + @TelephonyManager.DataEnabledReason int reason) { try { - sRegistry.notifyDataEnabled(enabled, reason); + sRegistry.notifyDataEnabled(slotIndex, subId, enabled, reason); } catch (RemoteException ex) { // system server crash } @@ -801,11 +802,11 @@ public class TelephonyRegistryManager { /** * Notify emergency number list changed on certain subscription. * - * @param subId for which emergency number list changed. * @param slotIndex for which emergency number list changed. Can be derived from subId except * when subId is invalid. + * @param subId for which emergency number list changed. */ - public void notifyAllowedNetworkTypesChanged(int subId, int slotIndex, + public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId, Map<Integer, Long> allowedNetworkTypeList) { try { sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, allowedNetworkTypeList); diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index aef185c77633..33b7c757f1e8 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -18,6 +18,7 @@ package android.text; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -101,7 +102,7 @@ public final class FontConfig implements Parcelable { * * If there is no update, this return 0. */ - public long getLastModifiedTimeMillis() { + public @CurrentTimeMillisLong long getLastModifiedTimeMillis() { return mLastModifiedTimeMillis; } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index b22921233f05..2b577d04b18d 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -46,6 +46,9 @@ public class FeatureFlagUtils { "settings_do_not_restore_preserved"; /** @hide */ public static final String SETTINGS_PROVIDER_MODEL = "settings_provider_model"; + /** @hide */ + public static final String SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES + = "settings_use_new_backup_eligibility_rules"; private static final Map<String, String> DEFAULT_FLAGS; @@ -68,6 +71,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_silky_home", "false"); DEFAULT_FLAGS.put("settings_contextual_home", "false"); DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false"); + DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/util/MergedConfiguration.java b/core/java/android/util/MergedConfiguration.java index 3ac44aacc2c7..4328e0826bd0 100644 --- a/core/java/android/util/MergedConfiguration.java +++ b/core/java/android/util/MergedConfiguration.java @@ -33,9 +33,9 @@ import java.io.PrintWriter; */ public class MergedConfiguration implements Parcelable { - private Configuration mGlobalConfig = new Configuration(); - private Configuration mOverrideConfig = new Configuration(); - private Configuration mMergedConfig = new Configuration(); + private final Configuration mGlobalConfig = new Configuration(); + private final Configuration mOverrideConfig = new Configuration(); + private final Configuration mMergedConfig = new Configuration(); public MergedConfiguration() { } @@ -59,15 +59,15 @@ public class MergedConfiguration implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mGlobalConfig, flags); - dest.writeParcelable(mOverrideConfig, flags); - dest.writeParcelable(mMergedConfig, flags); + mGlobalConfig.writeToParcel(dest, flags); + mOverrideConfig.writeToParcel(dest, flags); + mMergedConfig.writeToParcel(dest, flags); } public void readFromParcel(Parcel source) { - mGlobalConfig = source.readParcelable(Configuration.class.getClassLoader()); - mOverrideConfig = source.readParcelable(Configuration.class.getClassLoader()); - mMergedConfig = source.readParcelable(Configuration.class.getClassLoader()); + mGlobalConfig.readFromParcel(source); + mOverrideConfig.readFromParcel(source); + mMergedConfig.readFromParcel(source); } @Override diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java index 698cb77947cf..0ac2c9c25ad1 100644 --- a/core/java/android/util/RotationUtils.java +++ b/core/java/android/util/RotationUtils.java @@ -24,6 +24,7 @@ import static android.view.Surface.ROTATION_90; import android.annotation.Dimension; import android.graphics.Insets; import android.graphics.Matrix; +import android.graphics.Rect; import android.view.Surface.Rotation; /** @@ -73,6 +74,60 @@ public class RotationUtils { } /** + * Rotates bounds as if parentBounds and bounds are a group. The group is rotated from + * oldRotation to newRotation. This assumes that parentBounds is at 0,0 and remains at 0,0 after + * rotation. The bounds will be at the same physical position in parentBounds. + * + * Only 'inOutBounds' is mutated. + */ + public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int oldRotation, + @Rotation int newRotation) { + rotateBounds(inOutBounds, parentBounds, deltaRotation(oldRotation, newRotation)); + } + + /** + * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` + * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and + * remains at 0,0 after rotation. The bounds will be at the same physical position in + * parentBounds. + * + * Only 'inOutBounds' is mutated. + */ + public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int rotation) { + final int origLeft = inOutBounds.left; + final int origTop = inOutBounds.top; + switch (rotation) { + case ROTATION_0: + return; + case ROTATION_90: + inOutBounds.left = inOutBounds.top; + inOutBounds.top = parentBounds.right - inOutBounds.right; + inOutBounds.right = inOutBounds.bottom; + inOutBounds.bottom = parentBounds.right - origLeft; + return; + case ROTATION_180: + inOutBounds.left = parentBounds.right - inOutBounds.right; + inOutBounds.right = parentBounds.right - origLeft; + inOutBounds.top = parentBounds.bottom - inOutBounds.bottom; + inOutBounds.bottom = parentBounds.bottom - origTop; + return; + case ROTATION_270: + inOutBounds.left = parentBounds.bottom - inOutBounds.bottom; + inOutBounds.bottom = inOutBounds.right; + inOutBounds.right = parentBounds.bottom - inOutBounds.top; + inOutBounds.top = origLeft; + } + } + + /** @return the rotation needed to rotate from oldRotation to newRotation. */ + @Rotation + public static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + /** * Sets a matrix such that given a rotation, it transforms physical display * coordinates to that rotation's logical coordinates. * diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl index b9c55081a103..468a69c7bddb 100644 --- a/core/java/android/uwb/IUwbAdapter.aidl +++ b/core/java/android/uwb/IUwbAdapter.aidl @@ -17,7 +17,6 @@ package android.uwb; import android.os.PersistableBundle; -import android.uwb.AngleOfArrivalSupport; import android.uwb.IUwbAdapterStateCallbacks; import android.uwb.IUwbRangingCallbacks; import android.uwb.SessionHandle; @@ -47,43 +46,6 @@ interface IUwbAdapter { void unregisterAdapterStateCallbacks(in IUwbAdapterStateCallbacks callbacks); /** - * Returns true if ranging is supported, false otherwise - */ - boolean isRangingSupported(); - - /** - * Get the angle of arrival supported by this device - * - * @return the angle of arrival type supported - */ - AngleOfArrivalSupport getAngleOfArrivalSupport(); - - /** - * Generates a list of the supported 802.15.4z channels - * - * The list must be prioritized in the order of preferred channel usage. - * - * The list must only contain channels that are permitted to be used in the - * device's current location. - * - * @return an array of support channels on the device for the current location. - */ - int[] getSupportedChannels(); - - /** - * Generates a list of the supported 802.15.4z preamble codes - * - * The list must be prioritized in the order of preferred preamble usage. - * - * The list must only contain preambles that are permitted to be used in the - * device's current location. - * - * @return an array of supported preambles on the device for the current - * location. - */ - int[] getSupportedPreambleCodes(); - - /** * Get the accuracy of the ranging timestamps * * @return accuracy of the ranging timestamps in nanoseconds @@ -91,27 +53,6 @@ interface IUwbAdapter { long getTimestampResolutionNanos(); /** - * Get the supported number of simultaneous ranging sessions - * - * @return the supported number of simultaneous ranging sessions - */ - int getMaxSimultaneousSessions(); - - /** - * Get the maximum number of remote devices per session when local device is initiator - * - * @return the maximum number of remote devices supported in a single session - */ - int getMaxRemoteDevicesPerInitiatorSession(); - - /** - * Get the maximum number of remote devices per session when local device is responder - * - * @return the maximum number of remote devices supported in a single session - */ - int getMaxRemoteDevicesPerResponderSession(); - - /** * Provides the capabilities and features of the device * * @return specification specific capabilities and features of the device diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index 2dc0ba0b9b80..63a6d058f358 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -32,10 +32,6 @@ import android.os.ServiceManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import java.util.concurrent.Executor; /** @@ -195,133 +191,6 @@ public final class UwbManager { } /** - * Check if ranging is supported, regardless of ranging method - * - * @return true if ranging is supported - */ - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public boolean isRangingSupported() { - try { - return mUwbAdapter.isRangingSupported(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE, - ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D, - ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL, - ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL}) - public @interface AngleOfArrivalSupportType {} - - /** - * Indicate absence of support for angle of arrival measurement - */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE = 1; - - /** - * Indicate support for planar angle of arrival measurement, due to antenna - * limitation. Typically requires at least two antennas. - */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; - - /** - * Indicate support for three dimensional angle of arrival measurement. - * Typically requires at least three antennas. However, due to antenna - * arrangement, a platform may only support hemi-spherical azimuth angles - * ranging from -pi/2 to pi/2 - */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; - - /** - * Indicate support for three dimensional angle of arrival measurement. - * Typically requires at least three antennas. This mode supports full - * azimuth angles ranging from -pi to pi. - */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; - - /** - * Gets the {@link AngleOfArrivalSupportType} supported on this platform - * <p>Possible return values are - * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE}, - * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D}, - * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL}, - * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL}. - * - * @return angle of arrival type supported - */ - @AngleOfArrivalSupportType - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public int getAngleOfArrivalSupport() { - try { - switch (mUwbAdapter.getAngleOfArrivalSupport()) { - case AngleOfArrivalSupport.TWO_DIMENSIONAL: - return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D; - - case AngleOfArrivalSupport.THREE_DIMENSIONAL_HEMISPHERICAL: - return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL; - - case AngleOfArrivalSupport.THREE_DIMENSIONAL_SPHERICAL: - return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL; - - case AngleOfArrivalSupport.NONE: - default: - return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE; - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get a {@link List} of supported channel numbers based on the device's current location - * <p>The returned values are ordered by the system's desired ordered of use, with the first - * entry being the most preferred. - * - * <p>Channel numbers are defined based on the IEEE 802.15.4z standard for UWB. - * - * @return {@link List} of supported channel numbers ordered by preference - */ - @NonNull - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public List<Integer> getSupportedChannelNumbers() { - List<Integer> channels = new ArrayList<>(); - try { - for (int channel : mUwbAdapter.getSupportedChannels()) { - channels.add(channel); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return channels; - } - - /** - * Get a {@link List} of supported preamble code indices - * <p> Preamble code indices are defined based on the IEEE 802.15.4z standard for UWB. - * - * @return {@link List} of supported preamble code indices - */ - @NonNull - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public Set<Integer> getSupportedPreambleCodeIndices() { - Set<Integer> preambles = new HashSet<>(); - try { - for (int preamble : mUwbAdapter.getSupportedPreambleCodes()) { - preambles.add(preamble); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return preambles; - } - - /** * Get the timestamp resolution for events in nanoseconds * <p>This value defines the maximum error of all timestamps for events reported to * {@link RangingSession.Callback}. @@ -339,50 +208,6 @@ public final class UwbManager { } /** - * Get the number of simultaneous sessions allowed in the system - * - * @return the maximum allowed number of simultaneously open {@link RangingSession} instances. - */ - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public int getMaxSimultaneousSessions() { - try { - return mUwbAdapter.getMaxSimultaneousSessions(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get the maximum number of remote devices in a {@link RangingSession} when the local device - * is the initiator. - * - * @return the maximum number of remote devices per {@link RangingSession} - */ - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public int getMaxRemoteDevicesPerInitiatorSession() { - try { - return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get the maximum number of remote devices in a {@link RangingSession} when the local device - * is a responder. - * - * @return the maximum number of remote devices per {@link RangingSession} - */ - @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public int getMaxRemoteDevicesPerResponderSession() { - try { - return mUwbAdapter.getMaxRemoteDevicesPerResponderSession(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Open a {@link RangingSession} with the given parameters * <p>The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a * {@link RangingSession} object used to control ranging when the session is successfully diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 8117c963b959..0ba1dfee16f3 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -34,7 +34,6 @@ import android.graphics.ColorSpace; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; @@ -1182,18 +1181,6 @@ public final class Display { } /** - * Returns the product-specific information about the display or the directly connected - * device on the display chain. - * For example, if the display is transitively connected, this field may contain product - * information about the intermediate device. - * Returns {@code null} if product information is not available. - */ - @Nullable - public DeviceProductInfo getDeviceProductInfo() { - return mDisplayInfo.deviceProductInfo; - } - - /** * Gets display metrics that describe the size and density of this display. * The size returned by this method does not necessarily represent the * actual raw size (native resolution) of the display. diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index 6543de15f969..eb49e52d5050 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -213,8 +213,7 @@ public final class FrameMetrics { Index.FRAME_TIMELINE_VSYNC_ID, Index.INTENDED_VSYNC, Index.VSYNC, - Index.OLDEST_INPUT_EVENT, - Index.NEWEST_INPUT_EVENT, + Index.INPUT_EVENT_ID, Index.HANDLE_INPUT_START, Index.ANIMATION_START, Index.PERFORM_TRAVERSALS_START, @@ -225,8 +224,11 @@ public final class FrameMetrics { Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS, Index.FRAME_COMPLETED, + Index.DEQUEUE_BUFFER_DURATION, + Index.QUEUE_BUFFER_DURATION, Index.GPU_COMPLETED, - Index.SWAP_BUFFERS_COMPLETED + Index.SWAP_BUFFERS_COMPLETED, + Index.DISPLAY_PRESENT_TIME, }) @Retention(RetentionPolicy.SOURCE) public @interface Index { @@ -234,20 +236,22 @@ public final class FrameMetrics { int FRAME_TIMELINE_VSYNC_ID = 1; int INTENDED_VSYNC = 2; int VSYNC = 3; - int OLDEST_INPUT_EVENT = 4; - int NEWEST_INPUT_EVENT = 5; - int HANDLE_INPUT_START = 6; - int ANIMATION_START = 7; - int PERFORM_TRAVERSALS_START = 8; - int DRAW_START = 9; - int FRAME_DEADLINE = 10; - int SYNC_QUEUED = 11; - int SYNC_START = 12; - int ISSUE_DRAW_COMMANDS_START = 13; - int SWAP_BUFFERS = 14; - int FRAME_COMPLETED = 15; - int GPU_COMPLETED = 18; - int SWAP_BUFFERS_COMPLETED = 19; + int INPUT_EVENT_ID = 4; + int HANDLE_INPUT_START = 5; + int ANIMATION_START = 6; + int PERFORM_TRAVERSALS_START = 7; + int DRAW_START = 8; + int FRAME_DEADLINE = 9; + int SYNC_QUEUED = 10; + int SYNC_START = 11; + int ISSUE_DRAW_COMMANDS_START = 12; + int SWAP_BUFFERS = 13; + int FRAME_COMPLETED = 14; + int DEQUEUE_BUFFER_DURATION = 15; + int QUEUE_BUFFER_DURATION = 16; + int GPU_COMPLETED = 17; + int SWAP_BUFFERS_COMPLETED = 18; + int DISPLAY_PRESENT_TIME = 19; int FRAME_STATS_COUNT = 20; // must always be last and in sync with // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedTaskListener.aidl index 29c9c155e978..c31e67e59a99 100644 --- a/core/java/android/view/IPinnedStackListener.aidl +++ b/core/java/android/view/IPinnedTaskListener.aidl @@ -23,11 +23,11 @@ import android.graphics.Rect; import android.view.DisplayInfo; /** - * Listener for changes to the pinned stack made by the WindowManager. + * Listener for changes to the pinned task made by the WindowManager. * * @hide */ -oneway interface IPinnedStackListener { +oneway interface IPinnedTaskListener { /** * Called when the window manager has detected a change that would cause the movement bounds diff --git a/core/java/android/view/IScrollCaptureCallbacks.aidl b/core/java/android/view/IScrollCaptureCallbacks.aidl index d97e3c66cc5d..26eaac0a2bf5 100644 --- a/core/java/android/view/IScrollCaptureCallbacks.aidl +++ b/core/java/android/view/IScrollCaptureCallbacks.aidl @@ -16,12 +16,10 @@ package android.view; -import android.graphics.Point; import android.graphics.Rect; +import android.view.ScrollCaptureResponse; import android.view.Surface; -import android.view.IScrollCaptureConnection; - /** * Asynchronous callback channel for responses to scroll capture requests. * @@ -29,34 +27,30 @@ import android.view.IScrollCaptureConnection; */ interface IScrollCaptureCallbacks { /** - * Scroll capture is available, and a connection has been provided. + * Provides the result of WindowManagerService#requestScrollCapture * - * @param connection a connection to a window process and scrollable content - * @param scrollAreaInWindow the location of scrolling in global (window) coordinate space + * @param response the response which describes the result */ - oneway void onConnected(in IScrollCaptureConnection connection, in Rect scrollBounds, - in Point positionInWindow); + oneway void onScrollCaptureResponse(in ScrollCaptureResponse response); /** - * The window does not support scroll capture. - */ - oneway void onUnavailable(); - - /** - * Called when the remote end has confirmed the request and is ready to begin providing image - * requests. + * Called in reply to IScrollCaptureConnection#startCapture, when the remote end has confirmed + * the request and is ready to begin capturing images. */ oneway void onCaptureStarted(); /** - * Received a response from a capture request. + * Received a response from a capture request. The provided rectangle indicates the portion + * of the requested rectangle which was captured. An empty rectangle indicates that the request + * could not be satisfied (most commonly due to the available scrolling range). + * + * @param flags flags describing additional status of the result + * @param capturedArea the actual area of the image captured */ - oneway void onCaptureBufferSent(long frameNumber, in Rect capturedArea); + oneway void onImageRequestCompleted(int flags, in Rect capturedArea); /** - * Signals that the capture session has completed and the target window may be returned to - * normal interactive use. This may be due to normal shutdown, or after a timeout or other - * unrecoverable state change such as activity lifecycle, window visibility or focus. + * Signals that the capture session has completed and the target window is ready for normal use. */ - oneway void onConnectionClosed(); + oneway void onCaptureEnded(); } diff --git a/core/java/android/view/IScrollCaptureConnection.aidl b/core/java/android/view/IScrollCaptureConnection.aidl index 63a4f48aeb20..c55e88800393 100644 --- a/core/java/android/view/IScrollCaptureConnection.aidl +++ b/core/java/android/view/IScrollCaptureConnection.aidl @@ -17,33 +17,45 @@ package android.view; import android.graphics.Rect; +import android.os.ICancellationSignal; import android.view.Surface; /** - * Interface implemented by a client of the Scroll Capture framework to receive requests - * to start, capture images and end the session. + * A remote connection to a scroll capture target. * * {@hide} */ interface IScrollCaptureConnection { /** - * Informs the client that it has been selected for scroll capture and should prepare to - * to begin handling capture requests. + * Informs the target that it has been selected for scroll capture. + * + * @param surface a return channel for image buffers + * + * @return a cancallation signal which is used cancel the request + */ + ICancellationSignal startCapture(in Surface surface); + + /** + * Request the target capture an image within the provided rectangle. + * + * @param surface a return channel for image buffers + * @param signal a cancallation signal which can interrupt the request + * + * @return a cancallation signal which is used cancel the request */ - oneway void startCapture(in Surface surface); + ICancellationSignal requestImage(in Rect captureArea); /** - * Request the client capture an image within the provided rectangle. + * Inform the target that capture has ended. * - * @see android.view.ScrollCaptureCallback#onScrollCaptureRequest + * @return a cancallation signal which is used cancel the request */ - oneway void requestImage(in Rect captureArea); + ICancellationSignal endCapture(); /** - * Inform the client that capture has ended. The client should shut down and release all - * local resources in use and prepare for return to normal interactive usage. + * Closes the connection. */ - oneway void endCapture(); + oneway void close(); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 4e4ba3fad246..afdf798d03ce 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -35,13 +35,12 @@ import android.os.ParcelFileDescriptor; import android.view.DisplayCutout; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; -import android.view.IDockedStackListener; import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowRotationController; import android.view.IOnKeyguardExitResult; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.IScrollCaptureCallbacks; import android.view.RemoteAnimationAdapter; import android.view.IRotationWatcher; @@ -446,12 +445,12 @@ interface IWindowManager * Sets the region the user can touch the divider. This region will be excluded from the region * which is used to cause a focus switch when dispatching touch. */ - void setDockedStackDividerTouchRegion(in Rect touchableRegion); + void setDockedTaskDividerTouchRegion(in Rect touchableRegion); /** - * Registers a listener that will be called when the pinned stack state changes. + * Registers a listener that will be called when the pinned task state changes. */ - void registerPinnedStackListener(int displayId, IPinnedStackListener listener); + void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener); /** * Requests Keyboard Shortcuts from the displayed window. diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 5f2bccc8b857..e6cf68367ae6 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -261,11 +261,7 @@ public class InsetsSource implements Parcelable { public InsetsSource(Parcel in) { mType = in.readInt(); - if (in.readInt() != 0) { - mFrame = Rect.CREATOR.createFromParcel(in); - } else { - mFrame = null; - } + mFrame = Rect.CREATOR.createFromParcel(in); if (in.readInt() != 0) { mVisibleFrame = Rect.CREATOR.createFromParcel(in); } else { @@ -282,12 +278,7 @@ public class InsetsSource implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - if (mFrame != null) { - dest.writeInt(1); - mFrame.writeToParcel(dest, 0); - } else { - dest.writeInt(0); - } + mFrame.writeToParcel(dest, 0); if (mVisibleFrame != null) { dest.writeInt(1); mVisibleFrame.writeToParcel(dest, 0); diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index c717c2adc32d..1d4b4111f884 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -77,8 +77,8 @@ public class InsetsSourceControl implements Parcelable { public InsetsSourceControl(Parcel in) { mType = in.readInt(); - mLeash = in.readParcelable(null /* loader */); - mSurfacePosition = in.readParcelable(null /* loader */); + mLeash = in.readTypedObject(SurfaceControl.CREATOR); + mSurfacePosition = in.readTypedObject(Point.CREATOR); mSkipAnimationOnce = in.readBoolean(); } @@ -119,8 +119,8 @@ public class InsetsSourceControl implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - dest.writeParcelable(mLeash, 0 /* flags*/); - dest.writeParcelable(mSurfacePosition, 0 /* flags*/); + dest.writeTypedObject(mLeash, 0 /* parcelableFlags */); + dest.writeTypedObject(mSurfacePosition, 0 /* parcelableFlags */); dest.writeBoolean(mSkipAnimationOnce); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 21dd1fb05615..fce1952de44b 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -156,7 +156,7 @@ public class InsetsState implements Parcelable { static final int ISIDE_FLOATING = 4; static final int ISIDE_UNKNOWN = 5; - private InsetsSource[] mSources = new InsetsSource[SIZE]; + private final InsetsSource[] mSources = new InsetsSource[SIZE]; /** * The frame of the display these sources are relative to. @@ -804,7 +804,7 @@ public class InsetsState implements Parcelable { public void writeToParcel(Parcel dest, int flags) { mDisplayFrame.writeToParcel(dest, flags); mDisplayCutout.writeToParcel(dest, flags); - dest.writeParcelableArray(mSources, 0); + dest.writeTypedArray(mSources, 0 /* parcelableFlags */); dest.writeTypedObject(mRoundedCorners, flags); } @@ -820,9 +820,9 @@ public class InsetsState implements Parcelable { }; public void readFromParcel(Parcel in) { - mDisplayFrame.set(Rect.CREATOR.createFromParcel(in)); - mDisplayCutout.set(DisplayCutout.ParcelableWrapper.CREATOR.createFromParcel(in)); - mSources = in.readParcelableArray(null, InsetsSource.class); + mDisplayFrame.readFromParcel(in); + mDisplayCutout.readFromParcel(in); + in.readTypedArray(mSources, InsetsSource.CREATOR); mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); } diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index e66b17aa4426..c43c410ab995 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -77,3 +77,7 @@ per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/and per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/android/view/ScrollCaptureCallback.java b/core/java/android/view/ScrollCaptureCallback.java index d3aad2c72d27..16886160baca 100644 --- a/core/java/android/view/ScrollCaptureCallback.java +++ b/core/java/android/view/ScrollCaptureCallback.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.UiThread; import android.graphics.Rect; +import android.os.CancellationSignal; import java.util.function.Consumer; @@ -58,8 +59,6 @@ import java.util.function.Consumer; * @see View#setScrollCaptureHint(int) * @see View#setScrollCaptureCallback(ScrollCaptureCallback) * @see Window#registerScrollCaptureCallback(ScrollCaptureCallback) - * - * @hide */ @UiThread public interface ScrollCaptureCallback { @@ -68,80 +67,84 @@ public interface ScrollCaptureCallback { * The system is searching for the appropriate scrolling container to capture and would like to * know the size and position of scrolling content handled by this callback. * <p> - * Implementations should inset {@code containingViewBounds} to cover only the area within the - * containing view where scrolling content may be positioned. This should cover only the content - * which tracks with scrolling movement. - * <p> - * Return the updated rectangle to {@code resultConsumer}. If for any reason the scrolling - * content is not available to capture, a {@code null} rectangle may be returned, and this view - * will be excluded as the target for this request. + * To determine scroll bounds, an implementation should inset the visible bounds of the + * containing view to cover only the area where scrolling content may be positioned. This + * should cover only the content which tracks with scrolling movement. * <p> - * Responses received after XXXms will be discarded. + * Return the updated rectangle to {@link Consumer#accept onReady.accept}. If for any reason the + * scrolling content is not available to capture, a empty rectangle may be returned which will + * exclude this view from consideration. * <p> - * TODO: finalize timeout + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Consumer#accept onReady.accept} will have no effect and this + * content will be omitted from the search results. * - * @param onReady consumer for the updated rectangle + * @param signal signal to cancel the operation in progress + * @param onReady consumer for the updated rectangle */ - void onScrollCaptureSearch(@NonNull Consumer<Rect> onReady); + void onScrollCaptureSearch(@NonNull CancellationSignal signal, @NonNull Consumer<Rect> onReady); /** * Scroll Capture has selected this callback to provide the scrolling image content. * <p> - * The onReady signal should be called when ready to begin handling image requests. + * {@link Runnable#run onReady.run} should be called when ready to begin handling image + * requests. + * <p> + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Runnable#run onReady.run} will have no effect and provided session + * will not be activated. + * + * @param session the current session, resources provided by it are valid for use until the + * {@link #onScrollCaptureEnd(Runnable) session ends} + * @param signal signal to cancel the operation in progress + * @param onReady signal used to report completion of the request */ - void onScrollCaptureStart(@NonNull ScrollCaptureSession session, @NonNull Runnable onReady); + void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady); /** * An image capture has been requested from the scrolling content. * <p> - * <code>captureArea</code> contains the bounds of the image requested, relative to the - * rectangle provided by {@link ScrollCaptureCallback#onScrollCaptureSearch}, referred to as - * {@code scrollBounds}. - * here. - * <p> - * A series of requests will step by a constant vertical amount relative to {@code - * scrollBounds}, moving through the scrolling range of content, above and below the current - * visible area. The rectangle's vertical position will not account for any scrolling movement - * since capture started. Implementations therefore must track any scroll position changes and - * subtract this distance from requests. + * The requested rectangle describes an area inside the target view, relative to + * <code>scrollBounds</code>. The content may be offscreen, above or below the current visible + * portion of the target view. To handle the request, render the available portion of this + * rectangle to a buffer and return it via the Surface available from {@link + * ScrollCaptureSession#getSurface()}. * <p> - * To handle a request, the content should be scrolled to maximize the visible area of the - * requested rectangle. Offset {@code captureArea} again to account for any further scrolling. + * Note: Implementations are only required to render the requested content, and may do so into + * off-screen buffers without scrolling if they are able. * <p> - * Finally, clip this rectangle against scrollBounds to determine what portion, if any is - * visible content to capture. If the rectangle is completely clipped, set it to {@link - * Rect#setEmpty() empty} and skip the next step. - * <p> - * Make a copy of {@code captureArea}, transform to window coordinates and draw the window, - * clipped to this rectangle, into the {@link ScrollCaptureSession#getSurface() surface} at - * offset (0,0). - * <p> - * Finally, return the resulting {@code captureArea} using - * {@link ScrollCaptureSession#notifyBufferSent}. - * <p> - * If the response is not supplied within XXXms, the session will end with a call to {@link - * #onScrollCaptureEnd}, after which {@code session} is invalid and should be discarded. - * <p> - * TODO: finalize timeout + * The resulting available portion of the request must be computed as a portion of {@code + * captureArea}, and sent to signal the operation is complete, using {@link Consumer#accept + * onComplete.accept}. If the requested rectangle is partially or fully out of bounds the + * resulting portion should be returned. If no portion is available (outside of available + * content), then skip sending any buffer and report an empty Rect as result. * <p> + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Consumer#accept onComplete.accept} will be ignored until the next + * request. * + * @param session the current session, resources provided by it are valid for use until the + * {@link #onScrollCaptureEnd(Runnable) session ends} + * @param signal signal to cancel the operation in progress * @param captureArea the area to capture, a rectangle within {@code scrollBounds} + * @param onComplete a consumer for the captured area */ - void onScrollCaptureImageRequest( - @NonNull ScrollCaptureSession session, @NonNull Rect captureArea); + void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + @NonNull Consumer<Rect> onComplete); /** * Signals that capture has ended. Implementations should release any temporary resources or * references to objects in use during the capture. Any resources obtained from the session are * now invalid and attempts to use them after this point may throw an exception. * <p> - * The window should be returned as much as possible to its original state when capture started. - * At a minimum, the content should be scrolled to its original position. - * <p> - * <code>onReady</code> should be called when the window should be made visible and - * interactive. The system will wait up to XXXms for this call before proceeding. + * The window should be returned to its original state when capture started. At a minimum, the + * content should be scrolled to its original position. * <p> - * TODO: finalize timeout + * {@link Runnable#run onReady.run} should be called as soon as possible after the window is + * ready for normal interactive use. After the callback (or after a timeout, if not called) the + * screenshot tool will be dismissed and the window may become visible to the user at any time. * * @param onReady a callback to inform the system that the application has completed any * cleanup and is ready to become visible diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java index 0e6cdd1dbec5..3456e016c42c 100644 --- a/core/java/android/view/ScrollCaptureConnection.java +++ b/core/java/android/view/ScrollCaptureConnection.java @@ -18,18 +18,23 @@ package android.view; import static java.util.Objects.requireNonNull; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.UiThread; -import android.annotation.WorkerThread; import android.graphics.Point; import android.graphics.Rect; -import android.os.Handler; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; import android.os.RemoteException; +import android.os.Trace; import android.util.CloseGuard; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import java.util.concurrent.atomic.AtomicBoolean; +import java.lang.ref.WeakReference; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Mediator between a selected scroll capture target view and a remote process. @@ -41,270 +46,276 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub { private static final String TAG = "ScrollCaptureConnection"; - private static final int DEFAULT_TIMEOUT = 1000; - - private final Handler mHandler; - private ScrollCaptureTarget mSelectedTarget; - private int mTimeoutMillis = DEFAULT_TIMEOUT; - - protected Surface mSurface; - private IScrollCaptureCallbacks mCallbacks; + private final Object mLock = new Object(); private final Rect mScrollBounds; private final Point mPositionInWindow; private final CloseGuard mCloseGuard; + private final Executor mUiThread; + + private ScrollCaptureCallback mLocal; + private IScrollCaptureCallbacks mRemote; - // The current session instance in use by the callback. private ScrollCaptureSession mSession; - // Helps manage timeout callbacks registered to handler and aids testing. - private DelayedAction mTimeoutAction; + private CancellationSignal mCancellation; + + private volatile boolean mStarted; + private volatile boolean mConnected; /** * Constructs a ScrollCaptureConnection. * * @param selectedTarget the target the client is controlling - * @param callbacks the callbacks to reply to system requests + * @param remote the callbacks to reply to system requests * * @hide */ public ScrollCaptureConnection( + @NonNull Executor uiThread, @NonNull ScrollCaptureTarget selectedTarget, - @NonNull IScrollCaptureCallbacks callbacks) { + @NonNull IScrollCaptureCallbacks remote) { + mUiThread = requireNonNull(uiThread, "<uiThread> must non-null"); requireNonNull(selectedTarget, "<selectedTarget> must non-null"); - requireNonNull(callbacks, "<callbacks> must non-null"); - final Rect scrollBounds = requireNonNull(selectedTarget.getScrollBounds(), + mRemote = requireNonNull(remote, "<callbacks> must non-null"); + mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()), "target.getScrollBounds() must be non-null to construct a client"); - mSelectedTarget = selectedTarget; - mHandler = selectedTarget.getContainingView().getHandler(); - mScrollBounds = new Rect(scrollBounds); + mLocal = selectedTarget.getCallback(); mPositionInWindow = new Point(selectedTarget.getPositionInWindow()); - mCallbacks = callbacks; mCloseGuard = new CloseGuard(); mCloseGuard.open("close"); - - selectedTarget.getContainingView().addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - - } - - @Override - public void onViewDetachedFromWindow(View v) { - selectedTarget.getContainingView().removeOnAttachStateChangeListener(this); - endCapture(); - } - }); - } - - @VisibleForTesting - public void setTimeoutMillis(int timeoutMillis) { - mTimeoutMillis = timeoutMillis; + mConnected = true; } - @VisibleForTesting - public DelayedAction getTimeoutAction() { - return mTimeoutAction; - } - - private void checkConnected() { - if (mSelectedTarget == null || mCallbacks == null) { - throw new IllegalStateException("This client has been disconnected."); + @BinderThread + @Override + public ICancellationSignal startCapture(Surface surface) throws RemoteException { + checkConnected(); + if (!surface.isValid()) { + throw new RemoteException(new IllegalArgumentException("surface must be valid")); } - } - private void checkStarted() { - if (mSession == null) { - throw new IllegalStateException("Capture session has not been started!"); - } - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); + mSession = new ScrollCaptureSession(surface, mScrollBounds, mPositionInWindow); - @WorkerThread // IScrollCaptureConnection - @Override - public void startCapture(Surface surface) throws RemoteException { - checkConnected(); - mSurface = surface; - scheduleTimeout(mTimeoutMillis, this::onStartCaptureTimeout); - mSession = new ScrollCaptureSession(mSurface, mScrollBounds, mPositionInWindow, this); - mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureStart(mSession, - this::onStartCaptureCompleted)); + Runnable listener = + SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener)); + return cancellation; } @UiThread private void onStartCaptureCompleted() { - if (cancelTimeout()) { - mHandler.post(() -> { - try { - mCallbacks.onCaptureStarted(); - } catch (RemoteException e) { - doShutdown(); - } - }); + mStarted = true; + try { + mRemote.onCaptureStarted(); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); } } - @UiThread - private void onStartCaptureTimeout() { - endCapture(); - } - @WorkerThread // IScrollCaptureConnection + @BinderThread @Override - public void requestImage(Rect requestRect) { + public ICancellationSignal requestImage(Rect requestRect) throws RemoteException { + Trace.beginSection("requestImage"); checkConnected(); checkStarted(); - scheduleTimeout(mTimeoutMillis, this::onRequestImageTimeout); - // Response is dispatched via ScrollCaptureSession, to onRequestImageCompleted - mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureImageRequest( - mSession, new Rect(requestRect))); - } - @UiThread - void onRequestImageCompleted(long frameNumber, Rect capturedArea) { - final Rect finalCapturedArea = new Rect(capturedArea); - if (cancelTimeout()) { - mHandler.post(() -> { - try { - mCallbacks.onCaptureBufferSent(frameNumber, finalCapturedArea); - } catch (RemoteException e) { - doShutdown(); - } - }); - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); + + Consumer<Rect> listener = + SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest( + mSession, mCancellation, new Rect(requestRect), listener)); + Trace.endSection(); + return cancellation; } @UiThread - private void onRequestImageTimeout() { - endCapture(); + void onImageRequestCompleted(Rect capturedArea) { + try { + mRemote.onImageRequestCompleted(0, capturedArea); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); + } } - @WorkerThread // IScrollCaptureConnection + @BinderThread @Override - public void endCapture() { - if (isStarted()) { - scheduleTimeout(mTimeoutMillis, this::onEndCaptureTimeout); - mHandler.post(() -> - mSelectedTarget.getCallback().onScrollCaptureEnd(this::onEndCaptureCompleted)); - } else { - disconnect(); - } - } + public ICancellationSignal endCapture() throws RemoteException { + checkConnected(); + checkStarted(); - private boolean isStarted() { - return mCallbacks != null && mSelectedTarget != null; - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); - @UiThread - private void onEndCaptureCompleted() { // onEndCaptureCompleted - if (cancelTimeout()) { - doShutdown(); - } + Runnable listener = + SafeCallback.create(mCancellation, mUiThread, this::onEndCaptureCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureEnd(listener)); + return cancellation; } @UiThread - private void onEndCaptureTimeout() { - doShutdown(); + private void onEndCaptureCompleted() { + synchronized (mLock) { + mStarted = false; + try { + mRemote.onCaptureEnded(); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); + } + } } + @BinderThread + @Override + public void close() { + if (mStarted) { + Log.w(TAG, "close(): capture is still started?! Ending now."); - private void doShutdown() { - try { - if (mCallbacks != null) { - mCallbacks.onConnectionClosed(); - } - } catch (RemoteException e) { - // Ignore - } finally { - disconnect(); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ })); + mStarted = false; } + disconnect(); } /** * Shuts down this client and releases references to dependent objects. No attempt is made * to notify the controller, use with caution! */ - public void disconnect() { - if (mSession != null) { - mSession.disconnect(); + private void disconnect() { + synchronized (mLock) { mSession = null; + mConnected = false; + mStarted = false; + mRemote = null; + mLocal = null; + mCloseGuard.close(); } + } + + public boolean isConnected() { + return mConnected; + } - mSelectedTarget = null; - mCallbacks = null; + public boolean isStarted() { + return mStarted; + } + + private synchronized void checkConnected() throws RemoteException { + synchronized (mLock) { + if (!mConnected) { + throw new RemoteException(new IllegalStateException("Not connected")); + } + } + } + + private void checkStarted() throws RemoteException { + synchronized (mLock) { + if (!mStarted) { + throw new RemoteException(new IllegalStateException("Not started!")); + } + } } /** @return a string representation of the state of this client */ public String toString() { return "ScrollCaptureConnection{" + + "connected=" + mConnected + + ", started=" + mStarted + ", session=" + mSession - + ", selectedTarget=" + mSelectedTarget - + ", clientCallbacks=" + mCallbacks + + ", remote=" + mRemote + + ", local=" + mLocal + "}"; } - private boolean cancelTimeout() { - if (mTimeoutAction != null) { - return mTimeoutAction.cancel(); - } - return false; + @VisibleForTesting + public CancellationSignal getCancellation() { + return mCancellation; } - private void scheduleTimeout(long timeoutMillis, Runnable action) { - if (mTimeoutAction != null) { - mTimeoutAction.cancel(); + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); } - mTimeoutAction = new DelayedAction(mHandler, timeoutMillis, action); } - /** @hide */ - @VisibleForTesting - public static class DelayedAction { - private final AtomicBoolean mCompleted = new AtomicBoolean(); - private final Object mToken = new Object(); - private final Handler mHandler; - private final Runnable mAction; - - @VisibleForTesting - public DelayedAction(Handler handler, long timeoutMillis, Runnable action) { - mHandler = handler; - mAction = action; - mHandler.postDelayed(this::onTimeout, mToken, timeoutMillis); + private static class SafeCallback<T> { + private final CancellationSignal mSignal; + private final WeakReference<T> mTargetRef; + private final Executor mExecutor; + private boolean mExecuted; + + protected SafeCallback(CancellationSignal signal, Executor executor, T target) { + mSignal = signal; + mTargetRef = new WeakReference<>(target); + mExecutor = executor; } - private boolean onTimeout() { - if (mCompleted.compareAndSet(false, true)) { - mAction.run(); - return true; + // Provide the target to the consumer to invoke, forward on handler thread ONCE, + // and only if noy cancelled, and the target is still available (not collected) + protected final void maybeAccept(Consumer<T> targetConsumer) { + if (mExecuted) { + return; + } + mExecuted = true; + if (mSignal.isCanceled()) { + return; + } + T target = mTargetRef.get(); + if (target == null) { + return; } - return false; + mExecutor.execute(() -> targetConsumer.accept(target)); } - /** - * Cause the timeout action to run immediately and mark as timed out. - * - * @return true if the timeout was run, false if the timeout had already been canceled - */ - @VisibleForTesting - public boolean timeoutNow() { - return onTimeout(); + static Runnable create(CancellationSignal signal, Executor executor, Runnable target) { + return new RunnableCallback(signal, executor, target); } - /** - * Attempt to cancel the timeout action (such as after a callback is made) - * - * @return true if the timeout was canceled and will not run, false if time has expired and - * the timeout action has or will run momentarily - */ - public boolean cancel() { - if (!mCompleted.compareAndSet(false, true)) { - // Whoops, too late! - return false; - } - mHandler.removeCallbacksAndMessages(mToken); - return true; + static <T> Consumer<T> create(CancellationSignal signal, Executor executor, + Consumer<T> target) { + return new ConsumerCallback<T>(signal, executor, target); + } + } + + private static final class RunnableCallback extends SafeCallback<Runnable> implements Runnable { + RunnableCallback(CancellationSignal signal, Executor executor, Runnable target) { + super(signal, executor, target); + } + + @Override + public void run() { + maybeAccept(Runnable::run); + } + } + + private static final class ConsumerCallback<T> extends SafeCallback<Consumer<T>> + implements Consumer<T> { + ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target) { + super(signal, executor, target); + } + + @Override + public void accept(T value) { + maybeAccept((target) -> target.accept(value)); } } } diff --git a/core/java/android/view/ScrollCaptureResponse.aidl b/core/java/android/view/ScrollCaptureResponse.aidl new file mode 100644 index 000000000000..3de2b80ef16e --- /dev/null +++ b/core/java/android/view/ScrollCaptureResponse.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 android.view; + +parcelable ScrollCaptureResponse; diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java new file mode 100644 index 000000000000..564113edb3c7 --- /dev/null +++ b/core/java/android/view/ScrollCaptureResponse.java @@ -0,0 +1,381 @@ +/* + * 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.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; + +/** @hide */ +@DataClass(genToString = true, genGetters = true) +public class ScrollCaptureResponse implements Parcelable { + + /** Developer-facing human readable description of the result. */ + @NonNull + private String mDescription = ""; + + // Remaining fields are non-null when isConnected() == true + + /** The active connection for a successful result. */ + @Nullable + @DataClass.MaySetToNull + private IScrollCaptureConnection mConnection = null; + + /** The bounds of the window within the display */ + @Nullable + private Rect mWindowBounds = null; + + /** The bounds of the scrolling content, in window space. */ + @Nullable + private Rect mBoundsInWindow = null; + + /** The current window title. */ + @Nullable + private String mWindowTitle = null; + + /** Carries additional logging and debugging information when enabled. */ + @NonNull + @DataClass.PluralOf("message") + private ArrayList<String> mMessages = new ArrayList<>(); + + /** Whether a connection has been returned. */ + public boolean isConnected() { + return mConnection != null; + } + + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/ScrollCaptureResponse.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ ScrollCaptureResponse( + @NonNull String description, + @Nullable IScrollCaptureConnection connection, + @Nullable Rect windowBounds, + @Nullable Rect boundsInWindow, + @Nullable String windowTitle, + @NonNull ArrayList<String> messages) { + this.mDescription = description; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDescription); + this.mConnection = connection; + this.mWindowBounds = windowBounds; + this.mBoundsInWindow = boundsInWindow; + this.mWindowTitle = windowTitle; + this.mMessages = messages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMessages); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Developer-facing human readable description of the result. + */ + @DataClass.Generated.Member + public @NonNull String getDescription() { + return mDescription; + } + + /** + * The active connection for a successful result. + */ + @DataClass.Generated.Member + public @Nullable IScrollCaptureConnection getConnection() { + return mConnection; + } + + /** + * The bounds of the window within the display + */ + @DataClass.Generated.Member + public @Nullable Rect getWindowBounds() { + return mWindowBounds; + } + + /** + * The bounds of the scrolling content, in window space. + */ + @DataClass.Generated.Member + public @Nullable Rect getBoundsInWindow() { + return mBoundsInWindow; + } + + /** + * The current window title. + */ + @DataClass.Generated.Member + public @Nullable String getWindowTitle() { + return mWindowTitle; + } + + /** + * Carries additional logging and debugging information when enabled. + */ + @DataClass.Generated.Member + public @NonNull ArrayList<String> getMessages() { + return mMessages; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ScrollCaptureResponse { " + + "description = " + mDescription + ", " + + "connection = " + mConnection + ", " + + "windowBounds = " + mWindowBounds + ", " + + "boundsInWindow = " + mBoundsInWindow + ", " + + "windowTitle = " + mWindowTitle + ", " + + "messages = " + mMessages + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mConnection != null) flg |= 0x2; + if (mWindowBounds != null) flg |= 0x4; + if (mBoundsInWindow != null) flg |= 0x8; + if (mWindowTitle != null) flg |= 0x10; + dest.writeByte(flg); + dest.writeString(mDescription); + if (mConnection != null) dest.writeStrongInterface(mConnection); + if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags); + if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags); + if (mWindowTitle != null) dest.writeString(mWindowTitle); + dest.writeStringList(mMessages); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected ScrollCaptureResponse(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String description = in.readString(); + IScrollCaptureConnection connection = (flg & 0x2) == 0 ? null : IScrollCaptureConnection.Stub.asInterface(in.readStrongBinder()); + Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); + Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); + String windowTitle = (flg & 0x10) == 0 ? null : in.readString(); + ArrayList<String> messages = new ArrayList<>(); + in.readStringList(messages); + + this.mDescription = description; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDescription); + this.mConnection = connection; + this.mWindowBounds = windowBounds; + this.mBoundsInWindow = boundsInWindow; + this.mWindowTitle = windowTitle; + this.mMessages = messages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMessages); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ScrollCaptureResponse> CREATOR + = new Parcelable.Creator<ScrollCaptureResponse>() { + @Override + public ScrollCaptureResponse[] newArray(int size) { + return new ScrollCaptureResponse[size]; + } + + @Override + public ScrollCaptureResponse createFromParcel(@NonNull android.os.Parcel in) { + return new ScrollCaptureResponse(in); + } + }; + + /** + * A builder for {@link ScrollCaptureResponse} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder { + + private @NonNull String mDescription; + private @Nullable IScrollCaptureConnection mConnection; + private @Nullable Rect mWindowBounds; + private @Nullable Rect mBoundsInWindow; + private @Nullable String mWindowTitle; + private @NonNull ArrayList<String> mMessages; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Developer-facing human readable description of the result. + */ + @DataClass.Generated.Member + public @NonNull Builder setDescription(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mDescription = value; + return this; + } + + /** + * The active connection for a successful result. + */ + @DataClass.Generated.Member + public @NonNull Builder setConnection(@Nullable IScrollCaptureConnection value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mConnection = value; + return this; + } + + /** + * The bounds of the window within the display + */ + @DataClass.Generated.Member + public @NonNull Builder setWindowBounds(@NonNull Rect value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mWindowBounds = value; + return this; + } + + /** + * The bounds of the scrolling content, in window space. + */ + @DataClass.Generated.Member + public @NonNull Builder setBoundsInWindow(@NonNull Rect value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mBoundsInWindow = value; + return this; + } + + /** + * The current window title. + */ + @DataClass.Generated.Member + public @NonNull Builder setWindowTitle(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mWindowTitle = value; + return this; + } + + /** + * Carries additional logging and debugging information when enabled. + */ + @DataClass.Generated.Member + public @NonNull Builder setMessages(@NonNull ArrayList<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mMessages = value; + return this; + } + + /** @see #setMessages */ + @DataClass.Generated.Member + public @NonNull Builder addMessage(@NonNull String value) { + if (mMessages == null) setMessages(new ArrayList<>()); + mMessages.add(value); + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull ScrollCaptureResponse build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mDescription = ""; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mConnection = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mWindowBounds = null; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mBoundsInWindow = null; + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mWindowTitle = null; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mMessages = new ArrayList<>(); + } + ScrollCaptureResponse o = new ScrollCaptureResponse( + mDescription, + mConnection, + mWindowBounds, + mBoundsInWindow, + mWindowTitle, + mMessages); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x40) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1612282689462L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/ScrollCaptureSearchResults.java b/core/java/android/view/ScrollCaptureSearchResults.java new file mode 100644 index 000000000000..3469b9dc7103 --- /dev/null +++ b/core/java/android/view/ScrollCaptureSearchResults.java @@ -0,0 +1,271 @@ +/* + * 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.view; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.UiThread; +import android.graphics.Rect; +import android.os.CancellationSignal; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Collects nodes in the view hierarchy which have been identified as scrollable content. + * + * @hide + */ +@UiThread +public final class ScrollCaptureSearchResults { + private final Executor mExecutor; + private final List<ScrollCaptureTarget> mTargets; + private final CancellationSignal mCancel; + + private Runnable mOnCompleteListener; + private int mCompleted; + private boolean mComplete = true; + + public ScrollCaptureSearchResults(Executor executor) { + mExecutor = executor; + mTargets = new ArrayList<>(); + mCancel = new CancellationSignal(); + } + + // Public + + /** + * Add the given target to the results. + * + * @param target the target to consider + */ + public void addTarget(@NonNull ScrollCaptureTarget target) { + requireNonNull(target); + + mTargets.add(target); + mComplete = false; + final ScrollCaptureCallback callback = target.getCallback(); + final Consumer<Rect> consumer = new SearchRequest(target); + + // Defer so the view hierarchy scan completes first + mExecutor.execute( + () -> callback.onScrollCaptureSearch(mCancel, consumer)); + } + + public boolean isComplete() { + return mComplete; + } + + /** + * Provides a callback to be invoked as soon as all responses have been received from all + * targets to this point. + * + * @param onComplete listener to add + */ + public void setOnCompleteListener(Runnable onComplete) { + if (mComplete) { + onComplete.run(); + } else { + mOnCompleteListener = onComplete; + } + } + + /** + * Indicates whether the search results are empty. + * + * @return true if no targets have been added + */ + public boolean isEmpty() { + return mTargets.isEmpty(); + } + + /** + * Force the results to complete now, cancelling any pending requests and calling a complete + * listener if provided. + */ + public void finish() { + if (!mComplete) { + mCancel.cancel(); + signalComplete(); + } + } + + private void signalComplete() { + mComplete = true; + mTargets.sort(PRIORITY_ORDER); + if (mOnCompleteListener != null) { + mOnCompleteListener.run(); + mOnCompleteListener = null; + } + } + + @VisibleForTesting + public List<ScrollCaptureTarget> getTargets() { + return new ArrayList<>(mTargets); + } + + /** + * Get the top ranked result out of all completed requests. + * + * @return the top ranked result + */ + public ScrollCaptureTarget getTopResult() { + ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0); + return target != null && target.getScrollBounds() != null ? target : null; + } + + private class SearchRequest implements Consumer<Rect> { + private ScrollCaptureTarget mTarget; + + SearchRequest(ScrollCaptureTarget target) { + mTarget = target; + } + + @Override + public void accept(Rect scrollBounds) { + if (mTarget == null || mCancel.isCanceled()) { + return; + } + mExecutor.execute(() -> consume(scrollBounds)); + } + + private void consume(Rect scrollBounds) { + if (mTarget == null || mCancel.isCanceled()) { + return; + } + if (!nullOrEmpty(scrollBounds)) { + mTarget.setScrollBounds(scrollBounds); + mTarget.updatePositionInWindow(); + } + mCompleted++; + mTarget = null; + + // All done? + if (mCompleted == mTargets.size()) { + signalComplete(); + } + } + } + + private static final int AFTER = 1; + private static final int BEFORE = -1; + private static final int EQUAL = 0; + + static final Comparator<ScrollCaptureTarget> PRIORITY_ORDER = (a, b) -> { + if (a == null && b == null) { + return 0; + } else if (a == null || b == null) { + return (a == null) ? 1 : -1; + } + + boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds()); + boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds()); + if (emptyScrollBoundsA || emptyScrollBoundsB) { + if (emptyScrollBoundsA && emptyScrollBoundsB) { + return EQUAL; + } + // Prefer the one with a non-empty scroll bounds + if (emptyScrollBoundsA) { + return AFTER; + } + return BEFORE; + } + + final View viewA = a.getContainingView(); + final View viewB = b.getContainingView(); + + // Prefer any view with scrollCaptureHint="INCLUDE", over one without + // This is an escape hatch for the next rule (descendants first) + boolean hintIncludeA = hasIncludeHint(viewA); + boolean hintIncludeB = hasIncludeHint(viewB); + if (hintIncludeA != hintIncludeB) { + return (hintIncludeA) ? BEFORE : AFTER; + } + // If the views are relatives, prefer the descendant. This allows implementations to + // leverage nested scrolling APIs by interacting with the innermost scrollable view (as + // would happen with touch input). + if (isDescendant(viewA, viewB)) { + return BEFORE; + } + if (isDescendant(viewB, viewA)) { + return AFTER; + } + + // finally, prefer one with larger scroll bounds + int scrollAreaA = area(a.getScrollBounds()); + int scrollAreaB = area(b.getScrollBounds()); + return (scrollAreaA >= scrollAreaB) ? BEFORE : AFTER; + }; + + private static int area(Rect r) { + return r.width() * r.height(); + } + + private static boolean nullOrEmpty(Rect r) { + return r == null || r.isEmpty(); + } + + private static boolean hasIncludeHint(View view) { + return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0; + } + + /** + * Determines if {@code otherView} is a descendant of {@code view}. + * + * @param view a view + * @param otherView another view + * @return true if {@code view} is an ancestor of {@code otherView} + */ + private static boolean isDescendant(@NonNull View view, @NonNull View otherView) { + if (view == otherView) { + return false; + } + ViewParent otherParent = otherView.getParent(); + while (otherParent != view && otherParent != null) { + otherParent = otherParent.getParent(); + } + return otherParent == view; + } + + void dump(IndentingPrintWriter writer) { + writer.println("results:"); + writer.increaseIndent(); + writer.println("complete: " + isComplete()); + writer.println("cancelled: " + mCancel.isCanceled()); + writer.println("targets:"); + writer.increaseIndent(); + if (isEmpty()) { + writer.println("None"); + } else { + for (int i = 0; i < mTargets.size(); i++) { + writer.println("[" + i + "]"); + writer.increaseIndent(); + mTargets.get(i).dump(writer); + writer.decreaseIndent(); + } + writer.decreaseIndent(); + } + writer.decreaseIndent(); + } +} diff --git a/core/java/android/view/ScrollCaptureSession.java b/core/java/android/view/ScrollCaptureSession.java index 92617a325265..748e7ea56f41 100644 --- a/core/java/android/view/ScrollCaptureSession.java +++ b/core/java/android/view/ScrollCaptureSession.java @@ -16,18 +16,15 @@ package android.view; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; /** * A session represents the scope of interaction between a {@link ScrollCaptureCallback} and the - * system during an active scroll capture operation. During the scope of a session, a callback - * will receive a series of requests for image data. Resources provided here are valid for use - * until {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable)}. - * - * @hide + * system during an active scroll capture operation. */ public class ScrollCaptureSession { @@ -35,22 +32,27 @@ public class ScrollCaptureSession { private final Rect mScrollBounds; private final Point mPositionInWindow; - @Nullable - private ScrollCaptureConnection mConnection; - - /** @hide */ - public ScrollCaptureSession(Surface surface, Rect scrollBounds, Point positionInWindow, - ScrollCaptureConnection connection) { - mSurface = surface; - mScrollBounds = scrollBounds; - mPositionInWindow = positionInWindow; - mConnection = connection; + /** + * Constructs a new session instance. + * + * @param surface the surface to consume generated images + * @param scrollBounds the bounds of the capture area within the containing view + * @param positionInWindow the offset of scrollBounds within the window + */ + public ScrollCaptureSession(@NonNull Surface surface, @NonNull Rect scrollBounds, + @NonNull Point positionInWindow) { + mSurface = requireNonNull(surface); + mScrollBounds = requireNonNull(scrollBounds); + mPositionInWindow = requireNonNull(positionInWindow); } /** * Returns a * <a href="https://source.android.com/devices/graphics/arch-bq-gralloc">BufferQueue</a> in the * form of a {@link Surface} for transfer of image buffers. + * <p> + * The surface is guaranteed to remain {@link Surface#isValid() valid} until the session + * {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable) ends}. * * @return the surface for transferring image buffers * @throws IllegalStateException if the session has been closed @@ -80,26 +82,4 @@ public class ScrollCaptureSession { public Point getPositionInWindow() { return mPositionInWindow; } - - /** - * Notify the system that an a buffer has been posted via the getSurface() channel. - * - * @param frameNumber the frame number of the queued buffer - * @param capturedArea the area captured, relative to scroll bounds - */ - public void notifyBufferSent(long frameNumber, @NonNull Rect capturedArea) { - if (mConnection != null) { - mConnection.onRequestImageCompleted(frameNumber, capturedArea); - } - } - - /** - * @hide - */ - public void disconnect() { - mConnection = null; - if (mSurface.isValid()) { - mSurface.release(); - } - } } diff --git a/core/java/android/view/ScrollCaptureTarget.java b/core/java/android/view/ScrollCaptureTarget.java index f3fcabb26b31..4fd48892da70 100644 --- a/core/java/android/view/ScrollCaptureTarget.java +++ b/core/java/android/view/ScrollCaptureTarget.java @@ -22,14 +22,16 @@ import android.annotation.UiThread; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.os.CancellationSignal; import com.android.internal.util.FastMath; +import java.io.PrintWriter; +import java.util.function.Consumer; + /** * A target collects the set of contextual information for a ScrollCaptureHandler discovered during * a {@link View#dispatchScrollCaptureSearch scroll capture search}. - * - * @hide */ public final class ScrollCaptureTarget { private final View mContainingView; @@ -41,7 +43,6 @@ public final class ScrollCaptureTarget { private final float[] mTmpFloatArr = new float[2]; private final Matrix mMatrixViewLocalToWindow = new Matrix(); - private final Rect mTmpRect = new Rect(); public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect, @NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) { @@ -52,7 +53,10 @@ public final class ScrollCaptureTarget { mPositionInWindow = positionInWindow; } - /** @return the hint that the {@code containing view} had during the scroll capture search */ + /** + * @return the hint that the {@code containing view} had during the scroll capture search + * @see View#getScrollCaptureHint() + */ @View.ScrollCaptureHint public int getHint() { return mHint; @@ -71,8 +75,7 @@ public final class ScrollCaptureTarget { } /** - * Returns the un-clipped, visible bounds of the containing view during the scroll capture - * search. This is used to determine on-screen area to assist in selecting the primary target. + * Returns the visible bounds of the containing view. * * @return the visible bounds of the {@code containing view} in view-local coordinates */ @@ -81,13 +84,17 @@ public final class ScrollCaptureTarget { return mLocalVisibleRect; } - /** @return the position of the {@code containing view} within the window */ + /** @return the position of the visible bounds of the containing view within the window */ @NonNull public Point getPositionInWindow() { return mPositionInWindow; } - /** @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} */ + /** + * @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} + * + * @see ScrollCaptureCallback#onScrollCaptureSearch(CancellationSignal, Consumer) + */ @Nullable public Rect getScrollBounds() { return mScrollBounds; @@ -119,8 +126,8 @@ public final class ScrollCaptureTarget { } /** - * Refresh the value of {@link #mLocalVisibleRect} and {@link #mPositionInWindow} based on the - * current state of the {@code containing view}. + * Refresh the local visible bounds and it's offset within the window, based on the current + * state of the {@code containing view}. */ @UiThread public void updatePositionInWindow() { @@ -132,4 +139,27 @@ public final class ScrollCaptureTarget { roundIntoPoint(mPositionInWindow, mTmpFloatArr); } + public String toString() { + return "ScrollCaptureTarget{" + "view=" + mContainingView + + ", callback=" + mCallback + + ", scrollBounds=" + mScrollBounds + + ", localVisibleRect=" + mLocalVisibleRect + + ", positionInWindow=" + mPositionInWindow + + "}"; + } + + void dump(@NonNull PrintWriter writer) { + View view = getContainingView(); + writer.println("view: " + view); + writer.println("hint: " + mHint); + writer.println("callback: " + mCallback); + writer.println("scrollBounds: " + + (mScrollBounds == null ? "null" : mScrollBounds.toShortString())); + Point inWindow = getPositionInWindow(); + writer.println("positionInWindow: " + + ((inWindow == null) ? "null" : "[" + inWindow.x + "," + inWindow.y + "]")); + Rect localVisible = getLocalVisibleRect(); + writer.println("localVisibleRect: " + + (localVisible == null ? "null" : localVisible.toShortString())); + } } diff --git a/core/java/android/view/ScrollCaptureTargetResolver.java b/core/java/android/view/ScrollCaptureTargetResolver.java deleted file mode 100644 index e4316bbc9397..000000000000 --- a/core/java/android/view/ScrollCaptureTargetResolver.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.annotation.AnyThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UiThread; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - - -import java.util.Queue; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -/** - * Queries additional state from a list of {@link ScrollCaptureTarget targets} via asynchronous - * callbacks, then aggregates and reduces the target list to a single target, or null if no target - * is suitable. - * <p> - * The rules for selection are (in order): - * <ul> - * <li>prefer getScrollBounds(): non-empty - * <li>prefer View.getScrollCaptureHint == SCROLL_CAPTURE_HINT_INCLUDE - * <li>prefer descendants before parents - * <li>prefer larger area for getScrollBounds() (clipped to view bounds) - * </ul> - * - * <p> - * All calls to {@link ScrollCaptureCallback#onScrollCaptureSearch} are made on the main thread, - * with results are queued and consumed to the main thread as well. - * - * @see #start(Handler, long, Consumer) - * - * @hide - */ -@UiThread -public class ScrollCaptureTargetResolver { - private static final String TAG = "ScrollCaptureTargetRes"; - - private final Object mLock = new Object(); - - private final Queue<ScrollCaptureTarget> mTargets; - private Handler mHandler; - private long mTimeLimitMillis; - - private Consumer<ScrollCaptureTarget> mWhenComplete; - private int mPendingBoundsRequests; - private long mDeadlineMillis; - - private ScrollCaptureTarget mResult; - private boolean mFinished; - - private boolean mStarted; - - private static int area(Rect r) { - return r.width() * r.height(); - } - - private static boolean nullOrEmpty(Rect r) { - return r == null || r.isEmpty(); - } - - /** - * Binary operator which selects the best {@link ScrollCaptureTarget}. - */ - private static ScrollCaptureTarget chooseTarget(ScrollCaptureTarget a, ScrollCaptureTarget b) { - if (a == null && b == null) { - return null; - } else if (a == null || b == null) { - ScrollCaptureTarget c = (a == null) ? b : a; - return c; - } - - boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds()); - boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds()); - if (emptyScrollBoundsA || emptyScrollBoundsB) { - if (emptyScrollBoundsA && emptyScrollBoundsB) { - // Both have an empty or null scrollBounds - Log.d(TAG, "chooseTarget: (both have empty or null bounds) return " + null); - return null; - } - // Prefer the one with a non-empty scroll bounds - if (emptyScrollBoundsA) { - Log.d(TAG, "chooseTarget: (a has empty or null bounds) return " + b); - return b; - } - Log.d(TAG, "chooseTarget: (b has empty or null bounds) return " + a); - return a; - } - - final View viewA = a.getContainingView(); - final View viewB = b.getContainingView(); - - // Prefer any view with scrollCaptureHint="INCLUDE", over one without - // This is an escape hatch for the next rule (descendants first) - boolean hintIncludeA = hasIncludeHint(viewA); - boolean hintIncludeB = hasIncludeHint(viewB); - if (hintIncludeA != hintIncludeB) { - ScrollCaptureTarget c = (hintIncludeA) ? a : b; - Log.d(TAG, "chooseTarget: (has hint=INCLUDE) return " + c); - return c; - } - - // If the views are relatives, prefer the descendant. This allows implementations to - // leverage nested scrolling APIs by interacting with the innermost scrollable view (as - // would happen with touch input). - if (isDescendant(viewA, viewB)) { - Log.d(TAG, "chooseTarget: (b is descendant of a) return " + b); - return b; - } - if (isDescendant(viewB, viewA)) { - Log.d(TAG, "chooseTarget: (a is descendant of b) return " + a); - return a; - } - - // finally, prefer one with larger scroll bounds - int scrollAreaA = area(a.getScrollBounds()); - int scrollAreaB = area(b.getScrollBounds()); - ScrollCaptureTarget c = (scrollAreaA >= scrollAreaB) ? a : b; - Log.d(TAG, "chooseTarget: return " + c); - return c; - } - - /** - * Creates an instance to query and filter {@code target}. - * - * @param targets a list of {@link ScrollCaptureTarget} as collected by {@link - * View#dispatchScrollCaptureSearch}. - * @see #start(Handler, long, Consumer) - */ - public ScrollCaptureTargetResolver(Queue<ScrollCaptureTarget> targets) { - mTargets = targets; - } - - void checkThread() { - if (mHandler.getLooper() != Looper.myLooper()) { - throw new IllegalStateException("Called from wrong thread! (" - + Thread.currentThread().getName() + ")"); - } - } - - /** - * Blocks until a result is returned (after completion or timeout). - * <p> - * For testing only. Normal usage should receive a callback after calling {@link #start}. - */ - @VisibleForTesting - public ScrollCaptureTarget waitForResult() throws InterruptedException { - synchronized (mLock) { - while (!mFinished) { - mLock.wait(); - } - } - return mResult; - } - - private void supplyResult(ScrollCaptureTarget target) { - checkThread(); - if (mFinished) { - return; - } - mResult = chooseTarget(mResult, target); - boolean finish = mPendingBoundsRequests == 0 - || SystemClock.uptimeMillis() >= mDeadlineMillis; - if (finish) { - mPendingBoundsRequests = 0; - mWhenComplete.accept(mResult); - synchronized (mLock) { - mFinished = true; - mLock.notify(); - } - mWhenComplete = null; - } - } - - /** - * Asks all targets for {@link ScrollCaptureCallback#onScrollCaptureSearch(Consumer) - * scrollBounds}, and selects the primary target according to the {@link - * #chooseTarget} function. - * - * @param timeLimitMillis the amount of time to wait for all responses before delivering the top - * result - * @param resultConsumer the consumer to receive the primary target - */ - @AnyThread - public void start(Handler uiHandler, long timeLimitMillis, - Consumer<ScrollCaptureTarget> resultConsumer) { - synchronized (mLock) { - if (mStarted) { - throw new IllegalStateException("already started!"); - } - if (timeLimitMillis < 0) { - throw new IllegalArgumentException("Time limit must be positive"); - } - mHandler = uiHandler; - mTimeLimitMillis = timeLimitMillis; - mWhenComplete = resultConsumer; - if (mTargets.isEmpty()) { - mHandler.post(() -> supplyResult(null)); - return; - } - mStarted = true; - uiHandler.post(this::run); - } - } - - private void run() { - checkThread(); - - mPendingBoundsRequests = mTargets.size(); - for (ScrollCaptureTarget target : mTargets) { - queryTarget(target); - } - mDeadlineMillis = SystemClock.uptimeMillis() + mTimeLimitMillis; - mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis); - } - - private final Runnable mTimeoutRunnable = () -> { - checkThread(); - supplyResult(null); - }; - - /** - * Adds a target to the list and requests {@link ScrollCaptureCallback#onScrollCaptureSearch} - * scrollBounds} from it. Results are returned by a call to {@link #onScrollBoundsProvided}. - * - * @param target the target to add - */ - @UiThread - private void queryTarget(@NonNull ScrollCaptureTarget target) { - checkThread(); - final ScrollCaptureCallback callback = target.getCallback(); - // from the UI thread, request scroll bounds - callback.onScrollCaptureSearch( - // allow only one callback to onReady.accept(): - new SingletonConsumer<Rect>( - // Queue and consume on the UI thread - ((scrollBounds) -> mHandler.post( - () -> onScrollBoundsProvided(target, scrollBounds))))); - } - - @UiThread - private void onScrollBoundsProvided(ScrollCaptureTarget target, @Nullable Rect scrollBounds) { - checkThread(); - if (mFinished) { - return; - } - - // Record progress. - mPendingBoundsRequests--; - - // Remove the timeout. - mHandler.removeCallbacks(mTimeoutRunnable); - - boolean doneOrTimedOut = mPendingBoundsRequests == 0 - || SystemClock.uptimeMillis() >= mDeadlineMillis; - - final View containingView = target.getContainingView(); - if (!nullOrEmpty(scrollBounds) && containingView.isAggregatedVisible()) { - target.updatePositionInWindow(); - target.setScrollBounds(scrollBounds); - supplyResult(target); - } - - if (!mFinished) { - // Reschedule the timeout. - mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis); - } - } - - private static boolean hasIncludeHint(View view) { - return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0; - } - - /** - * Determines if {@code otherView} is a descendant of {@code view}. - * - * @param view a view - * @param otherView another view - * @return true if {@code view} is an ancestor of {@code otherView} - */ - private static boolean isDescendant(@NonNull View view, @NonNull View otherView) { - if (view == otherView) { - return false; - } - ViewParent otherParent = otherView.getParent(); - while (otherParent != view && otherParent != null) { - otherParent = otherParent.getParent(); - } - return otherParent == view; - } - - /** - * A safe wrapper for a consumer callbacks intended to accept a single value. It ensures - * that the receiver of the consumer does not retain a reference to {@code target} after use nor - * cause race conditions by invoking {@link Consumer#accept accept} more than once. - */ - static class SingletonConsumer<T> implements Consumer<T> { - final AtomicReference<Consumer<T>> mAtomicRef; - - /** - * @param target the target consumer - **/ - SingletonConsumer(Consumer<T> target) { - mAtomicRef = new AtomicReference<>(target); - } - - @Override - public void accept(T t) { - final Consumer<T> consumer = mAtomicRef.getAndSet(null); - if (consumer != null) { - consumer.accept(t); - } - } - } -} diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java index 8d891bbc2cfc..f177451783dc 100644 --- a/core/java/android/view/SoundEffectConstants.java +++ b/core/java/android/view/SoundEffectConstants.java @@ -16,12 +16,21 @@ package android.view; +import android.media.AudioManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.Random; + /** - * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} + * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} */ public class SoundEffectConstants { private SoundEffectConstants() {} + private static final Random NAVIGATION_REPEAT_RANDOMIZER = new Random(); + private static int sLastNavigationRepeatSoundEffectId = -1; public static final int CLICK = 0; @@ -29,6 +38,14 @@ public class SoundEffectConstants { public static final int NAVIGATION_UP = 2; public static final int NAVIGATION_RIGHT = 3; public static final int NAVIGATION_DOWN = 4; + /** Sound effect for a repeatedly triggered navigation, e.g. due to long pressing a button */ + public static final int NAVIGATION_REPEAT_LEFT = 5; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_UP = 6; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_RIGHT = 7; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_DOWN = 8; /** * Get the sonification constant for the focus directions. @@ -40,7 +57,7 @@ public class SoundEffectConstants { * @throws {@link IllegalArgumentException} when the passed direction is not one of the * documented values. */ - public static int getContantForFocusDirection(int direction) { + public static int getContantForFocusDirection(@View.FocusDirection int direction) { switch (direction) { case View.FOCUS_RIGHT: return SoundEffectConstants.NAVIGATION_RIGHT; @@ -56,4 +73,65 @@ public class SoundEffectConstants { throw new IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}."); } + + /** + * Get the sonification constant for the focus directions + * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD} + * or {@link View#FOCUS_BACKWARD} + * @param repeating True if the user long-presses a direction + * @return The appropriate sonification constant + * @throws IllegalArgumentException when the passed direction is not one of the + * documented values. + */ + public static int getConstantForFocusDirection(@View.FocusDirection int direction, + boolean repeating) { + if (repeating) { + switch (direction) { + case View.FOCUS_RIGHT: + return SoundEffectConstants.NAVIGATION_REPEAT_RIGHT; + case View.FOCUS_FORWARD: + case View.FOCUS_DOWN: + return SoundEffectConstants.NAVIGATION_REPEAT_DOWN; + case View.FOCUS_LEFT: + return SoundEffectConstants.NAVIGATION_REPEAT_LEFT; + case View.FOCUS_BACKWARD: + case View.FOCUS_UP: + return SoundEffectConstants.NAVIGATION_REPEAT_UP; + } + throw new IllegalArgumentException("direction must be one of {FOCUS_UP, FOCUS_DOWN, " + + "FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}."); + } else { + return getContantForFocusDirection(direction); + } + } + + /** + * @param effectId any of the effect ids defined in {@link SoundEffectConstants} + * @return true if the given effect id is a navigation repeat one + * @hide + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static boolean isNavigationRepeat(int effectId) { + return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_UP; + } + + /** + * @return The next navigation repeat sound effect id, chosen at random in a non-repeating + * fashion + * @hide + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static int nextNavigationRepeatSoundEffectId() { + int next = NAVIGATION_REPEAT_RANDOMIZER.nextInt( + AudioManager.NUM_NAVIGATION_REPEAT_SOUND_EFFECTS - 1); + if (next >= sLastNavigationRepeatSoundEffectId) { + next++; + } + sLastNavigationRepeatSoundEffectId = next; + return AudioManager.getNthNavigationRepeatSoundEffect(next); + } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 0832578d80c5..03dd10050724 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3504,64 +3504,4 @@ public final class SurfaceControl implements Parcelable { public static Transaction getGlobalTransaction() { return sGlobalTransaction; } - - /** - * Wrapper for sending blur data to SurfaceFlinger. - * @hide - */ - public static final class BlurRegion { - public int blurRadius; - public float cornerRadiusTL; - public float cornerRadiusTR; - public float cornerRadiusBL; - public float cornerRadiusBR; - public float alpha = 1; - public boolean visible = true; - public final Rect rect = new Rect(); - - private final float[] mFloatArray = new float[10]; - - public BlurRegion() { - } - - public BlurRegion(BlurRegion other) { - rect.set(other.rect); - blurRadius = other.blurRadius; - alpha = other.alpha; - cornerRadiusTL = other.cornerRadiusTL; - cornerRadiusTR = other.cornerRadiusTR; - cornerRadiusBL = other.cornerRadiusBL; - cornerRadiusBR = other.cornerRadiusBR; - } - - /** - * Serializes this class into a float array that's more JNI friendly. - */ - public float[] toFloatArray() { - mFloatArray[0] = blurRadius; - mFloatArray[1] = alpha; - mFloatArray[2] = rect.left; - mFloatArray[3] = rect.top; - mFloatArray[4] = rect.right; - mFloatArray[5] = rect.bottom; - mFloatArray[6] = cornerRadiusTL; - mFloatArray[7] = cornerRadiusTR; - mFloatArray[8] = cornerRadiusBL; - mFloatArray[9] = cornerRadiusBR; - return mFloatArray; - } - - @Override - public String toString() { - return "BlurRegion{" - + "blurRadius=" + blurRadius - + ", corners={" + cornerRadiusTL - + "," + cornerRadiusTR - + "," + cornerRadiusBL - + "," + cornerRadiusBR - + "}, alpha=" + alpha - + ", rect=" + rect - + "}"; - } - } } diff --git a/core/java/android/view/SurfaceControlFpsListener.java b/core/java/android/view/SurfaceControlFpsListener.java new file mode 100644 index 000000000000..517b0fb8ccd3 --- /dev/null +++ b/core/java/android/view/SurfaceControlFpsListener.java @@ -0,0 +1,93 @@ +/* + * 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.view; + +import android.annotation.NonNull; + +/** + * Listener for sampling the frames per second for a SurfaceControl and its children. + * This should only be used by a system component that needs to listen to a SurfaceControl's + * tree's FPS when it is not actively submitting transactions for that SurfaceControl. + * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used. + * + * @hide + */ +public abstract class SurfaceControlFpsListener { + private long mNativeListener; + + public SurfaceControlFpsListener() { + mNativeListener = nativeCreate(this); + } + + protected void destroy() { + if (mNativeListener == 0) { + return; + } + unregister(); + nativeDestroy(mNativeListener); + mNativeListener = 0; + } + + @Override + protected void finalize() throws Throwable { + try { + destroy(); + } finally { + super.finalize(); + } + } + + /** + * Reports the fps from the registered SurfaceControl + */ + public abstract void onFpsReported(float fps); + + /** + * Registers the sampling listener. + */ + public void register(@NonNull SurfaceControl layer) { + if (mNativeListener == 0) { + return; + } + + nativeRegister(mNativeListener, layer.mNativeObject); + } + + /** + * Unregisters the sampling listener. + */ + public void unregister() { + if (mNativeListener == 0) { + return; + } + nativeUnregister(mNativeListener); + } + + /** + * Dispatch the collected sample. + * + * Called from native code on a binder thread. + */ + private static void dispatchOnFpsReported(SurfaceControlFpsListener listener, float fps) { + listener.onFpsReported(fps); + } + + private static native long nativeCreate(SurfaceControlFpsListener thiz); + private static native void nativeDestroy(long ptr); + private static native void nativeRegister(long ptr, long layerObject); + private static native void nativeUnregister(long ptr); +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3789324a038c..ebef4646b0d9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -184,10 +184,10 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Queue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -1484,7 +1484,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_AUTO = 0; @@ -1495,7 +1494,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 0x1; @@ -1506,7 +1504,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_INCLUDE = 0x2; @@ -1517,7 +1514,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 0x4; @@ -30053,8 +30049,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Returns the current scroll capture hint for this view. * * @return the current scroll capture hint - * - * @hide */ @ScrollCaptureHint public int getScrollCaptureHint() { @@ -30067,8 +30061,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * scroll capture targets. * * @param hint the scrollCaptureHint flags value to set - * - * @hide */ public void setScrollCaptureHint(@ScrollCaptureHint int hint) { mPrivateFlags4 &= ~PFLAG4_SCROLL_CAPTURE_HINT_MASK; @@ -30088,10 +30080,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * setting a custom callback to help ensure it is selected as the target. * * @param callback the new callback to assign - * - * @hide */ - public void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) { + public final void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) { getListenerInfo().mScrollCaptureCallback = callback; } @@ -30110,29 +30100,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Dispatch a scroll capture search request down the view hierarchy. + * + * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to + * the parent + * @param windowOffset the offset of this view within the window + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns + */ + public void dispatchScrollCaptureSearch( + @NonNull Rect localVisibleRect, @NonNull Point windowOffset, + @NonNull Consumer<ScrollCaptureTarget> targets) { + onScrollCaptureSearch(localVisibleRect, windowOffset, targets); + } + + /** * Called when scroll capture is requested, to search for appropriate content to scroll. If * applicable, this view adds itself to the provided list for consideration, subject to the * flags set by {@link #setScrollCaptureHint}. * * @param localVisibleRect the local visible rect of this view * @param windowOffset the offset of localVisibleRect within the window - * @param targets a queue which collects potential targets - * + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns * @throws IllegalStateException if this view is not attached to a window - * @hide */ - public void dispatchScrollCaptureSearch(@NonNull Rect localVisibleRect, - @NonNull Point windowOffset, @NonNull Queue<ScrollCaptureTarget> targets) { + public void onScrollCaptureSearch(@NonNull Rect localVisibleRect, + @NonNull Point windowOffset, @NonNull Consumer<ScrollCaptureTarget> targets) { int hint = getScrollCaptureHint(); if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) { return; } + boolean rectIsVisible = true; + + // Apply clipBounds if present. + if (mClipBounds != null) { + rectIsVisible = localVisibleRect.intersect(mClipBounds); + } + if (!rectIsVisible) { + return; + } // Get a callback provided by the framework, library or application. ScrollCaptureCallback callback = (mListenerInfo == null) ? null : mListenerInfo.mScrollCaptureCallback; - // Try internal support for standard scrolling containers. + // Try framework support for standard scrolling containers. if (callback == null) { callback = createScrollCaptureCallbackInternal(localVisibleRect, windowOffset); } @@ -30142,7 +30157,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Add to the list for consideration Point offset = new Point(windowOffset.x, windowOffset.y); Rect rect = new Rect(localVisibleRect); - targets.add(new ScrollCaptureTarget(this, rect, offset, callback)); + targets.accept(new ScrollCaptureTarget(this, rect, offset, callback)); } } diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java index 890d071f8090..d4aaa611f800 100644 --- a/core/java/android/view/ViewFrameInfo.java +++ b/core/java/android/view/ViewFrameInfo.java @@ -58,8 +58,8 @@ public class ViewFrameInfo { public void populateFrameInfo(FrameInfo frameInfo) { frameInfo.frameInfo[FrameInfo.FLAGS] |= flags; frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart; - frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT] = oldestInputEventTime; - frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT] = newestInputEventTime; + // TODO(b/169866723): Use InputEventAssigner + frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime; } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 37bea5821e42..38a59373554c 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -77,7 +77,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Queue; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -7463,92 +7463,73 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Offsets the given rectangle in parent's local coordinates into child's coordinate space - * and clips the result to the child View's bounds, padding and clipRect if appropriate. If the - * resulting rectangle is not empty, the request is forwarded to the child. - * <p> - * Note: This method does not account for any static View transformations which may be - * applied to the child view. - * - * @param child the child to dispatch to - * @param localVisibleRect the visible (clipped) area of this ViewGroup, in local coordinates - * @param windowOffset the offset of localVisibleRect within the window - * @param targets a queue to collect located targets - */ - private void dispatchTransformedScrollCaptureSearch(View child, Rect localVisibleRect, - Point windowOffset, Queue<ScrollCaptureTarget> targets) { - - // copy local visible rect for modification and dispatch - final Rect childVisibleRect = getTempRect(); - childVisibleRect.set(localVisibleRect); - - // transform to child coords - final Point childWindowOffset = getTempPoint(); - childWindowOffset.set(windowOffset.x, windowOffset.y); - - final int dx = child.mLeft - mScrollX; - final int dy = child.mTop - mScrollY; - - childVisibleRect.offset(-dx, -dy); - childWindowOffset.offset(dx, dy); - - boolean rectIsVisible = true; - final int width = mRight - mLeft; - final int height = mBottom - mTop; - - // Clip to child bounds - if (getClipChildren()) { - rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), child.getHeight()); - } - - // Clip to child padding. - if (rectIsVisible && (child instanceof ViewGroup) - && ((ViewGroup) child).getClipToPadding()) { - rectIsVisible = childVisibleRect.intersect( - child.mPaddingLeft, child.mPaddingTop, - child.getWidth() - child.mPaddingRight, - child.getHeight() - child.mPaddingBottom); - } - // Clip to child clipBounds. - if (rectIsVisible && child.mClipBounds != null) { - rectIsVisible = childVisibleRect.intersect(child.mClipBounds); - } - if (rectIsVisible) { - child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets); - } - } - - /** * Handle the scroll capture search request by checking this view if applicable, then to each * child view. * * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to * the parent * @param windowOffset the offset of this view within the window - * @param targets the collected list of scroll capture targets - * - * @hide + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns */ @Override public void dispatchScrollCaptureSearch( @NonNull Rect localVisibleRect, @NonNull Point windowOffset, - @NonNull Queue<ScrollCaptureTarget> targets) { + @NonNull Consumer<ScrollCaptureTarget> targets) { + + // copy local visible rect for modification and dispatch + final Rect rect = getTempRect(); + rect.set(localVisibleRect); + + if (getClipToPadding()) { + rect.inset(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); + } // Dispatch to self first. super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); - // Then dispatch to children, if not excluding descendants. - if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) == 0) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - // Only visible views can be captured. - if (child.getVisibility() != View.VISIBLE) { - continue; - } - // Transform to child coords and dispatch - dispatchTransformedScrollCaptureSearch(child, localVisibleRect, windowOffset, - targets); + // Skip children if descendants excluded. + if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) { + return; + } + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + // Only visible views can be captured. + if (child.getVisibility() != View.VISIBLE) { + continue; + } + // Offset the given rectangle (in parent's local coordinates) into child's coordinate + // space and clip the result to the child View's bounds, padding and clipRect as needed. + // If the resulting rectangle is not empty, the request is forwarded to the child. + + // copy local visible rect for modification and dispatch + final Rect childVisibleRect = getTempRect(); + childVisibleRect.set(localVisibleRect); + + // transform to child coords + final Point childWindowOffset = getTempPoint(); + childWindowOffset.set(windowOffset.x, windowOffset.y); + + final int dx = child.mLeft - mScrollX; + final int dy = child.mTop - mScrollY; + + childVisibleRect.offset(-dx, -dy); + childWindowOffset.offset(dx, dy); + + boolean rectIsVisible = true; + + // Clip to child bounds + if (getClipChildren()) { + rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), + child.getHeight()); + } + + // Clip to child padding. + if (rectIsVisible) { + child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 144691d3eaa0..f8e65bd0d056 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -141,6 +141,7 @@ import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.LongArray; import android.util.MergedConfiguration; @@ -202,6 +203,7 @@ import com.android.internal.view.SurfaceCallbackHelper; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; @@ -274,6 +276,11 @@ public final class ViewRootImpl implements ViewParent, */ private static final int CONTENT_CAPTURE_ENABLED_FALSE = 2; + /** + * Maximum time to wait for {@link View#dispatchScrollCaptureSearch} to complete. + */ + private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -323,6 +330,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mForceDisableBLAST; private boolean mEnableTripleBuffering; + private boolean mFastScrollSoundEffectsEnabled; + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. @@ -666,8 +675,6 @@ public final class ViewRootImpl implements ViewParent, private final InsetsController mInsetsController; private final ImeFocusController mImeFocusController; - private ScrollCaptureConnection mScrollCaptureConnection; - private boolean mIsSurfaceOpaque; private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator = @@ -681,12 +688,6 @@ public final class ViewRootImpl implements ViewParent, return mImeFocusController; } - /** @return The current {@link ScrollCaptureConnection} for this instance, if any is active. */ - @Nullable - public ScrollCaptureConnection getScrollCaptureConnection() { - return mScrollCaptureConnection; - } - private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; @@ -728,6 +729,8 @@ public final class ViewRootImpl implements ViewParent, private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks; + private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; + /** * Increment this value when the surface has been replaced. */ @@ -813,6 +816,10 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); + + mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; } public static void addFirstDrawHandler(Runnable callback) { @@ -3962,11 +3969,12 @@ public final class ViewRootImpl implements ViewParent, } private void addFrameCallbackIfNeeded() { - boolean nextDrawUseBlastSync = mNextDrawUseBlastSync; - boolean hasBlur = mBlurRegionAggregator.hasRegions(); - boolean reportNextDraw = mReportNextDraw; + final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync; + final boolean reportNextDraw = mReportNextDraw; + final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates(); + final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions(); - if (!nextDrawUseBlastSync && !reportNextDraw && !hasBlur) { + if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) { return; } @@ -3974,18 +3982,22 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "Creating frameDrawingCallback" + " nextDrawUseBlastSync=" + nextDrawUseBlastSync + " reportNextDraw=" + reportNextDraw - + " hasBlur=" + hasBlur); + + " hasBlurUpdates=" + hasBlurUpdates); } - // The callback will run on a worker thread pool from the render thread. + final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame = + needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null; + + // The callback will run on the render thread. HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> { if (DEBUG_BLAST) { Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "." + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync); } - if (hasBlur) { - mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame); + if (needsCallbackForBlur) { + mBlurRegionAggregator + .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates); } if (mBlastBufferQueue == null) { @@ -6081,8 +6093,10 @@ public final class ViewRootImpl implements ViewParent, v, mTempRect); } if (v.requestFocus(direction, mTempRect)) { - playSoundEffect(SoundEffectConstants - .getContantForFocusDirection(direction)); + boolean isFastScrolling = event.getRepeatCount() > 0; + playSoundEffect( + SoundEffectConstants.getConstantForFocusDirection(direction, + isFastScrolling)); return true; } } @@ -7743,20 +7757,31 @@ public final class ViewRootImpl implements ViewParent, try { final AudioManager audioManager = getAudioManager(); + if (mFastScrollSoundEffectsEnabled + && SoundEffectConstants.isNavigationRepeat(effectId)) { + audioManager.playSoundEffect( + SoundEffectConstants.nextNavigationRepeatSoundEffectId()); + return; + } + switch (effectId) { case SoundEffectConstants.CLICK: audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); return; case SoundEffectConstants.NAVIGATION_DOWN: + case SoundEffectConstants.NAVIGATION_REPEAT_DOWN: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); return; case SoundEffectConstants.NAVIGATION_LEFT: + case SoundEffectConstants.NAVIGATION_REPEAT_LEFT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); return; case SoundEffectConstants.NAVIGATION_RIGHT: + case SoundEffectConstants.NAVIGATION_REPEAT_RIGHT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); return; case SoundEffectConstants.NAVIGATION_UP: + case SoundEffectConstants.NAVIGATION_REPEAT_UP: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP); return; default: @@ -9206,9 +9231,9 @@ public final class ViewRootImpl implements ViewParent, * Collect and include any ScrollCaptureCallback instances registered with the window. * * @see #addScrollCaptureCallback(ScrollCaptureCallback) - * @param targets the search queue for targets + * @param results an object to collect the results of the search */ - private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) { + private void collectRootScrollCaptureTargets(ScrollCaptureSearchResults results) { if (mRootScrollCaptureCallbacks == null) { return; } @@ -9216,26 +9241,45 @@ public final class ViewRootImpl implements ViewParent, // Add to the list for consideration Point offset = new Point(mView.getLeft(), mView.getTop()); Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight()); - targets.add(new ScrollCaptureTarget(mView, rect, offset, cb)); + results.addTarget(new ScrollCaptureTarget(mView, rect, offset, cb)); } } /** - * Handles an inbound request for scroll capture from the system. If a client is not already - * active, a search will be dispatched through the view tree to locate scrolling content. + * Update the timeout for scroll capture requests. Only affects this view root. + * The default value is {@link #SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS}. + * + * @param timeMillis the new timeout in milliseconds + */ + public void setScrollCaptureRequestTimeout(int timeMillis) { + mScrollCaptureRequestTimeout = timeMillis; + } + + /** + * Get the current timeout for scroll capture requests. + * + * @return the timeout in milliseconds + */ + public long getScrollCaptureRequestTimeout() { + return mScrollCaptureRequestTimeout; + } + + /** + * Handles an inbound request for scroll capture from the system. A search will be + * dispatched through the view tree to locate scrolling content. * <p> - * Either {@link IScrollCaptureCallbacks#onConnected(IScrollCaptureConnection, Rect, - * Point)} or {@link IScrollCaptureCallbacks#onUnavailable()} will be returned - * depending on the results of the search. + * A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)} + * will follow. * * @param callbacks to receive responses - * @see ScrollCaptureTargetResolver + * @see ScrollCaptureTargetSelector */ public void handleScrollCaptureRequest(@NonNull IScrollCaptureCallbacks callbacks) { - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = + new ScrollCaptureSearchResults(mContext.getMainExecutor()); // Window (root) level callbacks - collectRootScrollCaptureTargets(targetList); + collectRootScrollCaptureTargets(results); // Search through View-tree View rootView = getView(); @@ -9243,58 +9287,70 @@ public final class ViewRootImpl implements ViewParent, Point point = new Point(); Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight()); getChildVisibleRect(rootView, rect, point); - rootView.dispatchScrollCaptureSearch(rect, point, targetList); + rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget); } - - // No-op path. Scroll capture not offered for this window. - if (targetList.isEmpty()) { - dispatchScrollCaptureSearchResult(callbacks, null); - return; + Runnable onComplete = () -> dispatchScrollCaptureSearchResult(callbacks, results); + results.setOnCompleteListener(onComplete); + if (!results.isComplete()) { + mHandler.postDelayed(results::finish, getScrollCaptureRequestTimeout()); } - - // Request scrollBounds from each of the targets. - // Continues with the consumer once all responses are consumed, or the timeout expires. - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetList); - resolver.start(mHandler, 1000, - (selected) -> dispatchScrollCaptureSearchResult(callbacks, selected)); } /** Called by {@link #handleScrollCaptureRequest} when a result is returned */ private void dispatchScrollCaptureSearchResult( @NonNull IScrollCaptureCallbacks callbacks, - @Nullable ScrollCaptureTarget selectedTarget) { + @NonNull ScrollCaptureSearchResults results) { + + ScrollCaptureTarget selectedTarget = results.getTopResult(); + + ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder(); + response.setWindowTitle(getTitle().toString()); + + StringWriter writer = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(writer); + results.dump(pw); + pw.flush(); + response.addMessage(writer.toString()); - // If timeout or no eligible targets found. if (selectedTarget == null) { + response.setDescription("No scrollable targets found in window"); try { - if (DEBUG_SCROLL_CAPTURE) { - Log.d(TAG, "scrollCaptureSearch returned no targets available."); - } - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse(response.build()); } catch (RemoteException e) { - if (DEBUG_SCROLL_CAPTURE) { - Log.w(TAG, "Failed to send scroll capture search result.", e); - } + Log.e(TAG, "Failed to send scroll capture search result", e); } return; } - // Create a client instance and return it to the caller - mScrollCaptureConnection = new ScrollCaptureConnection(selectedTarget, callbacks); + response.setDescription("Connected"); + + // Compute area covered by scrolling content within window + Rect boundsInWindow = new Rect(); + View containingView = selectedTarget.getContainingView(); + containingView.getLocationInWindow(mAttachInfo.mTmpLocation); + boundsInWindow.set(selectedTarget.getScrollBounds()); + boundsInWindow.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]); + response.setBoundsInWindow(boundsInWindow); + + // Compute the area on screen covered by the window + Rect boundsOnScreen = new Rect(); + mView.getLocationOnScreen(mAttachInfo.mTmpLocation); + boundsOnScreen.set(0, 0, mView.getWidth(), mView.getHeight()); + boundsOnScreen.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]); + response.setWindowBounds(boundsOnScreen); + + // Create a connection and return it to the caller + ScrollCaptureConnection connection = new ScrollCaptureConnection( + mView.getContext().getMainExecutor(), selectedTarget, callbacks); + response.setConnection(connection); + try { - if (DEBUG_SCROLL_CAPTURE) { - Log.d(TAG, "scrollCaptureSearch returning client: " + getScrollCaptureConnection()); - } - callbacks.onConnected( - mScrollCaptureConnection, - selectedTarget.getScrollBounds(), - selectedTarget.getPositionInWindow()); + callbacks.onScrollCaptureResponse(response.build()); } catch (RemoteException e) { if (DEBUG_SCROLL_CAPTURE) { - Log.w(TAG, "Failed to send scroll capture search result.", e); + Log.w(TAG, "Failed to send scroll capture search response.", e); } - mScrollCaptureConnection.disconnect(); - mScrollCaptureConnection = null; + connection.close(); } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 4ecdd78f5a42..221b3346df58 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -2621,7 +2621,6 @@ public abstract class Window { * callback with the root view of the window. * * @param callback the callback to add - * @hide */ public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { } @@ -2630,7 +2629,6 @@ public abstract class Window { * Unregisters a {@link ScrollCaptureCallback} previously registered with this window. * * @param callback the callback to remove - * @hide */ public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 05b177ebbb45..39c09b46f2e1 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6612,6 +6612,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Returns the {@link EdgeEffect#getType()} for the edge effects. + * @return the {@link EdgeEffect#getType()} for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + @EdgeEffect.EdgeEffectType + public int getEdgeEffectType() { + return mEdgeGlowTop.getType(); + } + + /** + * Sets the {@link EdgeEffect#setType(int)} for the edge effects. + * @param type The edge effect type to use for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) { + mEdgeGlowTop.setType(type); + mEdgeGlowBottom.setType(type); + invalidate(); + } + + /** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources * associated to the View. diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 93b2d8ae3c9a..34fe51e82e8f 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -23,8 +23,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -68,12 +70,16 @@ public class AnalogClock extends View { @UnsupportedAppUsage private Drawable mHourHand; + private final TintInfo mHourHandTintInfo = new TintInfo(); @UnsupportedAppUsage private Drawable mMinuteHand; + private final TintInfo mMinuteHandTintInfo = new TintInfo(); @Nullable private Drawable mSecondHand; + private final TintInfo mSecondHandTintInfo = new TintInfo(); @UnsupportedAppUsage private Drawable mDial; + private final TintInfo mDialTintInfo = new TintInfo(); private int mDialWidth; private int mDialHeight; @@ -111,18 +117,86 @@ public class AnalogClock extends View { mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial); } + ColorStateList dialTintList = a.getColorStateList( + com.android.internal.R.styleable.AnalogClock_dialTint); + if (dialTintList != null) { + mDialTintInfo.mTintList = dialTintList; + mDialTintInfo.mHasTintList = true; + } + BlendMode dialTintMode = Drawable.parseBlendMode( + a.getInt(com.android.internal.R.styleable.AnalogClock_dialTintMode, -1), + null); + if (dialTintMode != null) { + mDialTintInfo.mTintBlendMode = dialTintMode; + mDialTintInfo.mHasTintBlendMode = true; + } + if (mDialTintInfo.mHasTintList || mDialTintInfo.mHasTintBlendMode) { + mDial = mDialTintInfo.apply(mDial); + } + mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); if (mHourHand == null) { mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour); } + ColorStateList hourHandTintList = a.getColorStateList( + com.android.internal.R.styleable.AnalogClock_hand_hourTint); + if (hourHandTintList != null) { + mHourHandTintInfo.mTintList = hourHandTintList; + mHourHandTintInfo.mHasTintList = true; + } + BlendMode hourHandTintMode = Drawable.parseBlendMode( + a.getInt(com.android.internal.R.styleable.AnalogClock_hand_hourTintMode, -1), + null); + if (hourHandTintMode != null) { + mHourHandTintInfo.mTintBlendMode = hourHandTintMode; + mHourHandTintInfo.mHasTintBlendMode = true; + } + if (mHourHandTintInfo.mHasTintList || mHourHandTintInfo.mHasTintBlendMode) { + mHourHand = mHourHandTintInfo.apply(mHourHand); + } + mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute); if (mMinuteHand == null) { mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute); } + ColorStateList minuteHandTintList = a.getColorStateList( + com.android.internal.R.styleable.AnalogClock_hand_minuteTint); + if (minuteHandTintList != null) { + mMinuteHandTintInfo.mTintList = minuteHandTintList; + mMinuteHandTintInfo.mHasTintList = true; + } + BlendMode minuteHandTintMode = Drawable.parseBlendMode( + a.getInt(com.android.internal.R.styleable.AnalogClock_hand_minuteTintMode, -1), + null); + if (minuteHandTintMode != null) { + mMinuteHandTintInfo.mTintBlendMode = minuteHandTintMode; + mMinuteHandTintInfo.mHasTintBlendMode = true; + } + if (mMinuteHandTintInfo.mHasTintList || mMinuteHandTintInfo.mHasTintBlendMode) { + mMinuteHand = mMinuteHandTintInfo.apply(mMinuteHand); + } + mSecondHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_second); + ColorStateList secondHandTintList = a.getColorStateList( + com.android.internal.R.styleable.AnalogClock_hand_secondTint); + if (secondHandTintList != null) { + mSecondHandTintInfo.mTintList = secondHandTintList; + mSecondHandTintInfo.mHasTintList = true; + } + BlendMode secondHandTintMode = Drawable.parseBlendMode( + a.getInt(com.android.internal.R.styleable.AnalogClock_hand_secondTintMode, -1), + null); + if (secondHandTintMode != null) { + mSecondHandTintInfo.mTintBlendMode = secondHandTintMode; + mSecondHandTintInfo.mHasTintBlendMode = true; + } + if (mSecondHandTintInfo.mHasTintList || mSecondHandTintInfo.mHasTintBlendMode) { + mSecondHand = mSecondHandTintInfo.apply(mSecondHand); + } + mTimeZone = toZoneId(a.getString(com.android.internal.R.styleable.AnalogClock_timeZone)); createClock(); @@ -141,6 +215,68 @@ public class AnalogClock extends View { invalidate(); } + /** + * Applies a tint to the dial drawable. + * <p> + * Subsequent calls to {@link #setDial(Icon)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#AnalogClock_dialTint + * @see #getDialTintList() + * @see Drawable#setTintList(ColorStateList) + */ + @RemotableViewMethod + public void setDialTintList(@Nullable ColorStateList tint) { + mDialTintInfo.mTintList = tint; + mDialTintInfo.mHasTintList = true; + + mDial = mDialTintInfo.apply(mDial); + } + + /** + * @return the tint applied to the dial drawable + * @attr ref android.R.styleable#AnalogClock_dialTint + * @see #setDialTintList(ColorStateList) + */ + @InspectableProperty(attributeId = com.android.internal.R.styleable.AnalogClock_dialTint) + @Nullable + public ColorStateList getDialTintList() { + return mDialTintInfo.mTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setDialTintList(ColorStateList)}} to the dial drawable. + * The default mode is {@link BlendMode#SRC_IN}. + * + * @param blendMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#AnalogClock_dialTintMode + * @see #getDialTintBlendMode() + * @see Drawable#setTintBlendMode(BlendMode) + */ + @RemotableViewMethod + public void setDialTintBlendMode(@Nullable BlendMode blendMode) { + mDialTintInfo.mTintBlendMode = blendMode; + mDialTintInfo.mHasTintBlendMode = true; + + mDial = mDialTintInfo.apply(mDial); + } + + /** + * @return the blending mode used to apply the tint to the dial drawable + * @attr ref android.R.styleable#AnalogClock_dialTintMode + * @see #setDialTintBlendMode(BlendMode) + */ + @InspectableProperty(attributeId = com.android.internal.R.styleable.AnalogClock_dialTintMode) + @Nullable + public BlendMode getDialTintBlendMode() { + return mDialTintInfo.mTintBlendMode; + } + /** Sets the hour hand of the clock to the specified Icon. */ @RemotableViewMethod public void setHourHand(@NonNull Icon icon) { @@ -150,6 +286,71 @@ public class AnalogClock extends View { invalidate(); } + /** + * Applies a tint to the hour hand drawable. + * <p> + * Subsequent calls to {@link #setHourHand(Icon)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#AnalogClock_hand_hourTint + * @see #getHourHandTintList() + * @see Drawable#setTintList(ColorStateList) + */ + @RemotableViewMethod + public void setHourHandTintList(@Nullable ColorStateList tint) { + mHourHandTintInfo.mTintList = tint; + mHourHandTintInfo.mHasTintList = true; + + mHourHand = mHourHandTintInfo.apply(mHourHand); + } + + /** + * @return the tint applied to the hour hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_hourTint + * @see #setHourHandTintList(ColorStateList) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_hourTint + ) + @Nullable + public ColorStateList getHourHandTintList() { + return mHourHandTintInfo.mTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setHourHandTintList(ColorStateList)}} to the hour hand drawable. + * The default mode is {@link BlendMode#SRC_IN}. + * + * @param blendMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#AnalogClock_hand_hourTintMode + * @see #getHourHandTintBlendMode() + * @see Drawable#setTintBlendMode(BlendMode) + */ + @RemotableViewMethod + public void setHourHandTintBlendMode(@Nullable BlendMode blendMode) { + mHourHandTintInfo.mTintBlendMode = blendMode; + mHourHandTintInfo.mHasTintBlendMode = true; + + mHourHand = mHourHandTintInfo.apply(mHourHand); + } + + /** + * @return the blending mode used to apply the tint to the hour hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_hourTintMode + * @see #setHourHandTintBlendMode(BlendMode) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_hourTintMode) + @Nullable + public BlendMode getHourHandTintBlendMode() { + return mHourHandTintInfo.mTintBlendMode; + } + /** Sets the minute hand of the clock to the specified Icon. */ @RemotableViewMethod public void setMinuteHand(@NonNull Icon icon) { @@ -160,6 +361,71 @@ public class AnalogClock extends View { } /** + * Applies a tint to the minute hand drawable. + * <p> + * Subsequent calls to {@link #setMinuteHand(Icon)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#AnalogClock_hand_minuteTint + * @see #getMinuteHandTintList() + * @see Drawable#setTintList(ColorStateList) + */ + @RemotableViewMethod + public void setMinuteHandTintList(@Nullable ColorStateList tint) { + mMinuteHandTintInfo.mTintList = tint; + mMinuteHandTintInfo.mHasTintList = true; + + mMinuteHand = mMinuteHandTintInfo.apply(mMinuteHand); + } + + /** + * @return the tint applied to the minute hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_minuteTint + * @see #setMinuteHandTintList(ColorStateList) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_minuteTint + ) + @Nullable + public ColorStateList getMinuteHandTintList() { + return mMinuteHandTintInfo.mTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setMinuteHandTintList(ColorStateList)}} to the minute hand drawable. + * The default mode is {@link BlendMode#SRC_IN}. + * + * @param blendMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#AnalogClock_hand_minuteTintMode + * @see #getMinuteHandTintBlendMode() + * @see Drawable#setTintBlendMode(BlendMode) + */ + @RemotableViewMethod + public void setMinuteHandTintBlendMode(@Nullable BlendMode blendMode) { + mMinuteHandTintInfo.mTintBlendMode = blendMode; + mMinuteHandTintInfo.mHasTintBlendMode = true; + + mMinuteHand = mMinuteHandTintInfo.apply(mMinuteHand); + } + + /** + * @return the blending mode used to apply the tint to the minute hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_minuteTintMode + * @see #setMinuteHandTintBlendMode(BlendMode) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_minuteTintMode) + @Nullable + public BlendMode getMinuteHandTintBlendMode() { + return mMinuteHandTintInfo.mTintBlendMode; + } + + /** * Sets the second hand of the clock to the specified Icon, or hides the second hand if it is * null. */ @@ -173,6 +439,71 @@ public class AnalogClock extends View { } /** + * Applies a tint to the second hand drawable. + * <p> + * Subsequent calls to {@link #setSecondHand(Icon)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#AnalogClock_hand_secondTint + * @see #getSecondHandTintList() + * @see Drawable#setTintList(ColorStateList) + */ + @RemotableViewMethod + public void setSecondHandTintList(@Nullable ColorStateList tint) { + mSecondHandTintInfo.mTintList = tint; + mSecondHandTintInfo.mHasTintList = true; + + mSecondHand = mSecondHandTintInfo.apply(mSecondHand); + } + + /** + * @return the tint applied to the second hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_secondTint + * @see #setSecondHandTintList(ColorStateList) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_secondTint + ) + @Nullable + public ColorStateList getSecondHandTintList() { + return mSecondHandTintInfo.mTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setSecondHandTintList(ColorStateList)}} to the second hand drawable. + * The default mode is {@link BlendMode#SRC_IN}. + * + * @param blendMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#AnalogClock_hand_secondTintMode + * @see #getSecondHandTintBlendMode() + * @see Drawable#setTintBlendMode(BlendMode) + */ + @RemotableViewMethod + public void setSecondHandTintBlendMode(@Nullable BlendMode blendMode) { + mSecondHandTintInfo.mTintBlendMode = blendMode; + mSecondHandTintInfo.mHasTintBlendMode = true; + + mSecondHand = mSecondHandTintInfo.apply(mSecondHand); + } + + /** + * @return the blending mode used to apply the tint to the second hand drawable + * @attr ref android.R.styleable#AnalogClock_hand_secondTintMode + * @see #setSecondHandTintBlendMode(BlendMode) + */ + @InspectableProperty( + attributeId = com.android.internal.R.styleable.AnalogClock_hand_secondTintMode) + @Nullable + public BlendMode getSecondHandTintBlendMode() { + return mSecondHandTintInfo.mTintBlendMode; + } + + /** * Indicates which time zone is currently used by this view. * * @return The ID of the current time zone or null if the default time zone, @@ -462,4 +793,36 @@ public class AnalogClock extends View { return null; } } + + private final class TintInfo { + boolean mHasTintList; + @Nullable ColorStateList mTintList; + boolean mHasTintBlendMode; + @Nullable BlendMode mTintBlendMode; + + /** + * Returns a mutated copy of {@code drawable} with tinting applied, or null if it's null. + */ + @Nullable + Drawable apply(@Nullable Drawable drawable) { + if (drawable == null) return null; + + Drawable newDrawable = drawable.mutate(); + + if (mHasTintList) { + newDrawable.setTintList(mTintList); + } + + if (mHasTintBlendMode) { + newDrawable.setTintBlendMode(mTintBlendMode); + } + + // All drawables should have the same state as the View itself. + if (drawable.isStateful()) { + newDrawable.setState(getDrawableState()); + } + + return newDrawable; + } + } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 6dedd12a2730..23915e06335a 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -303,6 +303,27 @@ public class HorizontalScrollView extends FrameLayout { } /** + * Returns the {@link EdgeEffect#getType()} for the edge effects. + * @return the {@link EdgeEffect#getType()} for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + @EdgeEffect.EdgeEffectType + public int getEdgeEffectType() { + return mEdgeGlowLeft.getType(); + } + + /** + * Sets the {@link EdgeEffect#setType(int)} for the edge effects. + * @param type The edge effect type to use for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) { + mEdgeGlowRight.setType(type); + mEdgeGlowLeft.setType(type); + invalidate(); + } + + /** * @return The maximum amount this scroll view will scroll in response to * an arrow event. */ diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 514471783c6a..e0b4ec71b0a0 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -46,6 +46,8 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.loader.ResourcesLoader; +import android.content.res.loader.ResourcesProvider; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Outline; @@ -62,16 +64,19 @@ import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; import android.os.StrictMode; import android.os.UserHandle; +import android.system.Os; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.SparseIntArray; import android.util.TypedValue; import android.util.TypedValue.ComplexDimensionUnit; import android.view.ContextThemeWrapper; @@ -92,6 +97,12 @@ import com.android.internal.R; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.Preconditions; +import java.io.ByteArrayOutputStream; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -520,8 +531,8 @@ public class RemoteViews implements Parcelable, Filter { * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ private abstract static class Action implements Parcelable { - public abstract void apply(View root, ViewGroup rootParent, - InteractionHandler handler) throws ActionException; + public abstract void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException; public static final int MERGE_REPLACE = 0; public static final int MERGE_APPEND = 1; @@ -551,8 +562,8 @@ public class RemoteViews implements Parcelable, Filter { * and return the final action which will run on the UI thread. * Override this if some of the tasks can be performed async. */ - public Action initActionAsync( - ViewTree root, ViewGroup rootParent, InteractionHandler handler) { + public Action initActionAsync(ViewTree root, ViewGroup rootParent, + InteractionHandler handler, ColorResources colorResources) { return this; } @@ -595,7 +606,9 @@ public class RemoteViews implements Parcelable, Filter { // Constant used during async execution. It is not parcelable. private static final Action ACTION_NOOP = new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { } + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { + } }; /** @@ -713,7 +726,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View view = root.findViewById(viewId); if (!(view instanceof AdapterView<?>)) return; @@ -748,7 +762,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -820,7 +835,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -843,7 +859,8 @@ public class RemoteViews implements Parcelable, Filter { if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { ((RemoteViewsListAdapter) a).setViewsList(list); } else { - v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); + v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, + colorResources)); } } else if (target instanceof AdapterViewAnimator) { AdapterViewAnimator v = (AdapterViewAnimator) target; @@ -851,7 +868,8 @@ public class RemoteViews implements Parcelable, Filter { if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { ((RemoteViewsListAdapter) a).setViewsList(list); } else { - v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); + v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, + colorResources)); } } } @@ -882,7 +900,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -919,7 +938,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); copy.isAsync = true; return copy; @@ -958,7 +977,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1037,7 +1057,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; if (!(target instanceof CompoundButton)) { @@ -1240,7 +1261,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1297,7 +1319,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1334,7 +1357,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View view = root.findViewById(viewId); if (view == null) return; @@ -1437,12 +1461,12 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, - InteractionHandler handler) throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { ReflectionAction ra = new ReflectionAction(viewId, methodName, BaseReflectionAction.BITMAP, bitmap); - ra.apply(root, rootParent, handler); + ra.apply(root, rootParent, handler, colorResources); } @Override @@ -1518,7 +1542,8 @@ public class RemoteViews implements Parcelable, Filter { protected abstract Object getParameterValue(@Nullable View view) throws ActionException; @Override - public final void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public final void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View view = root.findViewById(viewId); if (view == null) return; @@ -1536,7 +1561,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public final Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { final View view = root.findViewById(viewId); if (view == null) return ACTION_NOOP; @@ -1938,7 +1963,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { mRunnable.run(); } } @@ -1993,7 +2019,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final Context context = root.getContext(); final ViewGroup target = root.findViewById(viewId); @@ -2002,12 +2029,14 @@ public class RemoteViews implements Parcelable, Filter { } // Inflate nested views and add as children - target.addView(mNestedViews.apply(context, target, handler), mIndex); + target.addView( + mNestedViews.apply(context, target, handler, null /* size */, colorResources), + mIndex); } @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the current view. root.createTree(); @@ -2019,8 +2048,8 @@ public class RemoteViews implements Parcelable, Filter { // Inflate nested views and perform all the async tasks for the child remoteView. final Context context = root.mRoot.getContext(); - final AsyncApplyTask task = mNestedViews.getAsyncApplyTask( - context, targetVg, null, handler); + final AsyncApplyTask task = mNestedViews.getAsyncApplyTask(context, targetVg, + null /* listener */, handler, null /* size */, colorResources); final ViewTree tree = task.doInBackground(); if (tree == null) { @@ -2033,8 +2062,8 @@ public class RemoteViews implements Parcelable, Filter { return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) - throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { task.onPostExecute(tree); targetVg.addView(task.mResult, mIndex); } @@ -2095,7 +2124,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final ViewGroup target = root.findViewById(viewId); if (target == null) { @@ -2112,7 +2142,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the current view. root.createTree(); @@ -2136,8 +2166,8 @@ public class RemoteViews implements Parcelable, Filter { } return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) - throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { targetVg.removeAllViews(); return; @@ -2192,7 +2222,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null || target == root) { @@ -2207,7 +2238,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the correct view. root.createTree(); @@ -2226,8 +2257,8 @@ public class RemoteViews implements Parcelable, Filter { parent.mChildren.remove(target); return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) - throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { parentVg.removeView(target.mRoot); } }; @@ -2306,7 +2337,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final TextView target = root.findViewById(viewId); if (target == null) return; if (drawablesLoaded) { @@ -2337,7 +2369,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler) { + InteractionHandler handler, ColorResources colorResources) { final TextView target = root.findViewById(viewId); if (target == null) return ACTION_NOOP; @@ -2415,7 +2447,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final TextView target = root.findViewById(viewId); if (target == null) return; target.setTextSize(units, size); @@ -2460,7 +2493,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; target.setPadding(left, top, right, bottom); @@ -2533,7 +2567,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) { return; @@ -2646,7 +2681,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(viewId); if (target == null) return; @@ -2681,7 +2717,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { // Let's traverse the viewtree and override all textColors! Stack<View> viewsToProcess = new Stack<>(); viewsToProcess.add(root); @@ -2731,7 +2768,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) { final View target = root.findViewById(mViewId); if (target == null) return; @@ -2765,7 +2803,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -2816,8 +2855,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) - throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -2893,8 +2932,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler) - throws ActionException { + public void apply(View root, ViewGroup rootParent, InteractionHandler handler, + ColorResources colorResources) throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -4662,7 +4701,7 @@ public class RemoteViews implements Parcelable, Filter { RemoteViews rvToApply = getRemoteViewsToApply(context, size); View result = inflateView(context, rvToApply, parent); - rvToApply.performApply(result, parent, handler); + rvToApply.performApply(result, parent, handler, null); return result; } @@ -4674,27 +4713,39 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, - @Nullable InteractionHandler handler, - @StyleRes int applyThemeResId, + @Nullable InteractionHandler handler, @StyleRes int applyThemeResId, @Nullable PointF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); - View result = inflateView(context, rvToApply, parent, applyThemeResId); - rvToApply.performApply(result, parent, handler); + View result = inflateView(context, rvToApply, parent, applyThemeResId, null); + rvToApply.performApply(result, parent, handler, null); + return result; + } + + /** @hide */ + public View apply(Context context, ViewGroup parent, InteractionHandler handler, + @NonNull PointF size, @Nullable ColorResources colorResources) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); + + View result = inflateView(context, rvToApply, parent, 0, colorResources); + rvToApply.performApply(result, parent, handler, colorResources); return result; } private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { - return inflateView(context, rv, parent, 0); + return inflateView(context, rv, parent, 0, null); } private View inflateView(Context context, RemoteViews rv, ViewGroup parent, - @StyleRes int applyThemeResId) { + @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { // RemoteViews may be built by an application installed in another // user. So build a context that loads resources from that user but // still returns the current users userId so settings like data / time formats // are loaded without requiring cross user persmissions. final Context contextForResources = getContextForResources(context); + if (colorResources != null) { + colorResources.apply(contextForResources); + } Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); // If mApplyThemeResId is not given, Theme.DeviceDefault will be used. @@ -4756,34 +4807,37 @@ public class RemoteViews implements Parcelable, Filter { */ public CancellationSignal applyAsync( Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { - return applyAsync(context, parent, executor, listener, null); + return applyAsync(context, parent, executor, listener, null /* handler */); } /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler) { - return applyAsync(context, parent, executor, listener, handler, null); + return applyAsync(context, parent, executor, listener, handler, null /* size */); } /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, PointF size) { - return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor( - executor); + return getAsyncApplyTask(context, parent, listener, handler, size, null /* themeColors */) + .startTaskOnExecutor(executor); } - private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, - OnViewAppliedListener listener, InteractionHandler handler) { - return getAsyncApplyTask(context, parent, listener, handler, null); + /** @hide */ + public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, + OnViewAppliedListener listener, InteractionHandler handler, PointF size, + ColorResources colorResources) { + return getAsyncApplyTask(context, parent, listener, handler, size, colorResources) + .startTaskOnExecutor(executor); } private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, - OnViewAppliedListener listener, InteractionHandler handler, PointF size) { - return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, - listener, - handler, null); + OnViewAppliedListener listener, InteractionHandler handler, PointF size, + ColorResources colorResources) { + return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, + handler, colorResources, null /* result */); } private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> @@ -4794,6 +4848,7 @@ public class RemoteViews implements Parcelable, Filter { final Context mContext; final OnViewAppliedListener mListener; final InteractionHandler mHandler; + final ColorResources mColorResources; private View mResult; private ViewTree mTree; @@ -4802,11 +4857,12 @@ public class RemoteViews implements Parcelable, Filter { private AsyncApplyTask( RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, - InteractionHandler handler, View result) { + InteractionHandler handler, ColorResources colorResources, View result) { mRV = rv; mParent = parent; mContext = context; mListener = listener; + mColorResources = colorResources; mHandler = handler; mResult = result; @@ -4816,7 +4872,7 @@ public class RemoteViews implements Parcelable, Filter { protected ViewTree doInBackground(Void... params) { try { if (mResult == null) { - mResult = inflateView(mContext, mRV, mParent); + mResult = inflateView(mContext, mRV, mParent, 0, mColorResources); } mTree = new ViewTree(mResult); @@ -4825,7 +4881,8 @@ public class RemoteViews implements Parcelable, Filter { mActions = new Action[count]; for (int i = 0; i < count && !isCancelled(); i++) { // TODO: check if isCancelled in nested views. - mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler); + mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler, + mColorResources); } } else { mActions = null; @@ -4850,7 +4907,7 @@ public class RemoteViews implements Parcelable, Filter { InteractionHandler handler = mHandler == null ? DEFAULT_INTERACTION_HANDLER : mHandler; for (Action a : mActions) { - a.apply(viewTree.mRoot, mParent, handler); + a.apply(viewTree.mRoot, mParent, handler, mColorResources); } } } catch (Exception e) { @@ -4894,16 +4951,17 @@ public class RemoteViews implements Parcelable, Filter { * the {@link #apply(Context,ViewGroup)} call. */ public void reapply(Context context, View v) { - reapply(context, v, null, null); + reapply(context, v, null /* handler */); } /** @hide */ public void reapply(Context context, View v, InteractionHandler handler) { - reapply(context, v, handler, null); + reapply(context, v, handler, null /* size */, null /* colorResources */); } /** @hide */ - public void reapply(Context context, View v, InteractionHandler handler, PointF size) { + public void reapply(Context context, View v, InteractionHandler handler, PointF size, + ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation or size, is @@ -4917,7 +4975,7 @@ public class RemoteViews implements Parcelable, Filter { } } - rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); + rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); } /** @@ -4933,20 +4991,21 @@ public class RemoteViews implements Parcelable, Filter { * @return CancellationSignal * @hide */ - public CancellationSignal reapplyAsync( - Context context, View v, Executor executor, OnViewAppliedListener listener) { - return reapplyAsync(context, v, executor, listener, null, null); + public CancellationSignal reapplyAsync(Context context, View v, Executor executor, + OnViewAppliedListener listener) { + return reapplyAsync(context, v, executor, listener, null); } /** @hide */ public CancellationSignal reapplyAsync(Context context, View v, Executor executor, OnViewAppliedListener listener, InteractionHandler handler) { - return reapplyAsync(context, v, executor, listener, handler, null); + return reapplyAsync(context, v, executor, listener, handler, null, null); } /** @hide */ public CancellationSignal reapplyAsync(Context context, View v, Executor executor, - OnViewAppliedListener listener, InteractionHandler handler, PointF size) { + OnViewAppliedListener listener, InteractionHandler handler, PointF size, + ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation, is persisted @@ -4960,16 +5019,18 @@ public class RemoteViews implements Parcelable, Filter { } return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), - context, listener, handler, v).startTaskOnExecutor(executor); + context, listener, handler, colorResources, v).startTaskOnExecutor( + executor); } - private void performApply(View v, ViewGroup parent, InteractionHandler handler) { + private void performApply(View v, ViewGroup parent, InteractionHandler handler, + ColorResources colorResources) { if (mActions != null) { handler = handler == null ? DEFAULT_INTERACTION_HANDLER : handler; final int count = mActions.size(); for (int i = 0; i < count; i++) { Action a = mActions.get(i); - a.apply(v, parent, handler); + a.apply(v, parent, handler, colorResources); } } } @@ -5010,6 +5071,122 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Object allowing the modification of a context to overload the system's dynamic colors. + * + * Only colors from {@link android.R.color#system_primary_0} to + * {@link android.R.color#system_neutral_1000} can be overloaded. + * @hide + */ + public static final class ColorResources { + // Set of valid colors resources. + private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_primary_0; + private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_neutral_1000; + // Size, in bytes, of an entry in the array of colors in an ARSC file. + private static final int ARSC_ENTRY_SIZE = 16; + + private ResourcesLoader mLoader; + + private ColorResources(ResourcesLoader loader) { + mLoader = loader; + } + + /** + * Apply the color resources to the given context. + * + * No resource resolution must have be done on the context given to that method. + */ + public void apply(Context context) { + context.getResources().addLoaders(mLoader); + } + + private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException { + ByteArrayOutputStream content = new ByteArrayOutputStream(2048); + byte[] buffer = new byte[4096]; + while (input.available() > 0) { + int read = input.read(buffer); + content.write(buffer, 0, read); + } + return content; + } + + /** + * Creates the compiled resources content from the asset stored in the APK. + * + * The asset is a compiled resource with the correct resources name and correct ids, only + * the values are incorrect. The last value is at the very end of the file. The resources + * are in an array, the array's entries are 16 bytes each. We use this to work out the + * location of all the positions of the various resources. + */ + private static byte[] createCompiledResourcesContent(Context context, + SparseIntArray colorResources) throws IOException { + byte[] content; + try (InputStream input = context.getResources().openRawResource( + com.android.internal.R.raw.remote_views_color_resources)) { + ByteArrayOutputStream rawContent = readFileContent(input); + content = rawContent.toByteArray(); + } + int valuesOffset = + content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4; + if (valuesOffset < 0) { + Log.e(LOG_TAG, "ARSC file for theme colors is invalid."); + return null; + } + for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID; + colorRes++) { + // The last 2 bytes are the index in the color array. + int index = colorRes & 0xffff; + int offset = valuesOffset + index * ARSC_ENTRY_SIZE; + int value = colorResources.get(colorRes, context.getColor(colorRes)); + // Write the 32 bit integer in little endian + for (int b = 0; b < 4; b++) { + content[offset + b] = (byte) (value & 0xff); + value >>= 8; + } + } + return content; + } + + /** + * Adds a resource loader for theme colors to the given context. + * + * @param context Context of the view hosting the widget. + * @param colorMapping Mapping of resources to color values. + * + * @hide + */ + public static ColorResources create(Context context, SparseIntArray colorMapping) { + try { + byte[] contentBytes = createCompiledResourcesContent(context, colorMapping); + if (contentBytes == null) { + return null; + } + FileDescriptor arscFile = null; + try { + arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */); + // Note: This must not be closed through the OutputStream. + try (OutputStream pipeWriter = new FileOutputStream(arscFile)) { + pipeWriter.write(contentBytes); + + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) { + ResourcesLoader colorsLoader = new ResourcesLoader(); + colorsLoader.addProvider(ResourcesProvider + .loadFromTable(pfd, null /* assetsProvider */)); + return new ColorResources(colorsLoader); + } + } + } finally { + if (arscFile != null) { + Os.close(arscFile); + } + } + } catch (Exception ex) { + Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex); + } + return null; + } + } + + /** * Returns the number of actions in this RemoteViews. Can be used as a sequence number. * * @hide diff --git a/core/java/android/widget/RemoteViewsListAdapter.java b/core/java/android/widget/RemoteViewsListAdapter.java index b80fe4871616..827d03317d6a 100644 --- a/core/java/android/widget/RemoteViewsListAdapter.java +++ b/core/java/android/widget/RemoteViewsListAdapter.java @@ -31,12 +31,14 @@ public class RemoteViewsListAdapter extends BaseAdapter { private ArrayList<RemoteViews> mRemoteViewsList; private ArrayList<Integer> mViewTypes = new ArrayList<Integer>(); private int mViewTypeCount; + private RemoteViews.ColorResources mColorResources; public RemoteViewsListAdapter(Context context, ArrayList<RemoteViews> remoteViews, - int viewTypeCount) { + int viewTypeCount, RemoteViews.ColorResources colorResources) { mContext = context; mRemoteViewsList = remoteViews; mViewTypeCount = viewTypeCount; + mColorResources = colorResources; init(); } @@ -90,9 +92,10 @@ public class RemoteViewsListAdapter extends BaseAdapter { if (convertView != null && rv != null && convertView.getId() == rv.getLayoutId()) { v = convertView; - rv.reapply(mContext, v); + rv.reapply(mContext, v, null /* handler */, null /* size */, mColorResources); } else { - v = rv.apply(mContext, parent); + v = rv.apply(mContext, parent, null /* handler */, null /* size */, + mColorResources); } return v; } else { diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 64d09de49f2d..65f3da79afe0 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -335,6 +335,27 @@ public class ScrollView extends FrameLayout { } /** + * Returns the {@link EdgeEffect#getType()} for the edge effects. + * @return the {@link EdgeEffect#getType()} for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + @EdgeEffect.EdgeEffectType + public int getEdgeEffectType() { + return mEdgeGlowTop.getType(); + } + + /** + * Sets the {@link EdgeEffect#setType(int)} for the edge effects. + * @param type The edge effect type to use for the edge effects. + * @attr ref android.R.styleable#EdgeEffect_edgeEffectType + */ + public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) { + mEdgeGlowTop.setType(type); + mEdgeGlowBottom.setType(type); + invalidate(); + } + + /** * @return The maximum amount this scroll view will scroll in response to * an arrow event. */ diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index 2904a8c889a2..0f2a3ca6936b 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -184,7 +184,7 @@ public class ToastPresenter { mParams.y = yOffset; mParams.horizontalMargin = horizontalMargin; mParams.verticalMargin = verticalMargin; - addToastView(); + mView.setLayoutParams(mParams); } /** diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index e22a5eb9fe7b..acf9882e6658 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -58,9 +58,9 @@ public class ClientWindowFrames implements Parcelable { /** Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { - frame.set(Rect.CREATOR.createFromParcel(in)); - displayFrame.set(Rect.CREATOR.createFromParcel(in)); - backdropFrame.set(Rect.CREATOR.createFromParcel(in)); + frame.readFromParcel(in); + displayFrame.readFromParcel(in); + backdropFrame.readFromParcel(in); } @Override diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java index 96dac565eb3d..402d7fed90c5 100644 --- a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java +++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java @@ -19,6 +19,7 @@ package com.android.internal.graphics.drawable; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiThread; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -31,12 +32,14 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; -import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; -import android.view.SurfaceControl; +import android.util.LongSparseArray; import android.view.ViewRootImpl; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; /** * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state @@ -52,26 +55,40 @@ public final class BackgroundBlurDrawable extends Drawable { private final Paint mPaint = new Paint(); private final Path mRectPath = new Path(); private final float[] mTmpRadii = new float[8]; - private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion(); - // This will be called from a thread pool. - private final RenderNode.PositionUpdateListener mPositionUpdateListener = + private boolean mVisible = true; + + // Confined to UiThread. The values are copied into a BlurRegion, which lives on + // RenderThread to avoid interference with UiThread updates. + private int mBlurRadius; + private float mCornerRadiusTL; + private float mCornerRadiusTR; + private float mCornerRadiusBL; + private float mCornerRadiusBR; + private float mAlpha = 1; + + // Do not update from UiThread. This holds the latest position for this drawable. It is used + // by the Aggregator from RenderThread to get the final position of the blur region sent to SF + private final Rect mRect = new Rect(); + // This is called from a thread pool. The callbacks might come out of order w.r.t. the frame + // number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator + // can apply the update on RenderThread when processing that same frame. + @VisibleForTesting + public final RenderNode.PositionUpdateListener mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { - synchronized (mAggregator) { - mBlurRegion.rect.set(left, top, right, bottom); - mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion); - } + mAggregator.onRenderNodePositionChanged(frameNumber, () -> { + mRect.set(left, top, right, bottom); + }); } @Override public void positionLost(long frameNumber) { - synchronized (mAggregator) { - mBlurRegion.rect.setEmpty(); - mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion); - } + mAggregator.onRenderNodePositionChanged(frameNumber, () -> { + mRect.setEmpty(); + }); } }; @@ -79,6 +96,7 @@ public final class BackgroundBlurDrawable extends Drawable { mAggregator = aggregator; mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); mPaint.setColor(Color.TRANSPARENT); + mPaint.setAntiAlias(true); mRenderNode = new RenderNode("BackgroundBlurDrawable"); mRenderNode.addPositionUpdateListener(mPositionUpdateListener); } @@ -104,23 +122,30 @@ public final class BackgroundBlurDrawable extends Drawable { public boolean setVisible(boolean visible, boolean restart) { boolean changed = super.setVisible(visible, restart); if (changed) { - mBlurRegion.visible = visible; + mVisible = visible; + mAggregator.onBlurDrawableUpdated(this); } return changed; } @Override public void setAlpha(int alpha) { - mBlurRegion.alpha = alpha / 255f; - invalidateSelf(); + if (mAlpha != alpha / 255f) { + mAlpha = alpha / 255f; + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); + } } /** * Blur radius in pixels. */ public void setBlurRadius(int blurRadius) { - mBlurRegion.blurRadius = blurRadius; - invalidateSelf(); + if (mBlurRadius != blurRadius) { + mBlurRadius = blurRadius; + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); + } } /** @@ -139,14 +164,18 @@ public final class BackgroundBlurDrawable extends Drawable { */ public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL, float cornerRadiusBR) { - synchronized (mAggregator) { - mBlurRegion.cornerRadiusTL = cornerRadiusTL; - mBlurRegion.cornerRadiusTR = cornerRadiusTR; - mBlurRegion.cornerRadiusBL = cornerRadiusBL; - mBlurRegion.cornerRadiusBR = cornerRadiusBR; + if (mCornerRadiusTL != cornerRadiusTL + || mCornerRadiusTR != cornerRadiusTR + || mCornerRadiusBL != cornerRadiusBL + || mCornerRadiusBR != cornerRadiusBR) { + mCornerRadiusTL = cornerRadiusTL; + mCornerRadiusTR = cornerRadiusTR; + mCornerRadiusBL = cornerRadiusBL; + mCornerRadiusBR = cornerRadiusBR; + updatePath(); + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); } - updatePath(); - invalidateSelf(); } @Override @@ -157,12 +186,10 @@ public final class BackgroundBlurDrawable extends Drawable { } private void updatePath() { - synchronized (mAggregator) { - mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL; - mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR; - mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL; - mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR; - } + mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL; + mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR; + mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL; + mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR; mRectPath.reset(); if (getAlpha() == 0 || !isVisible()) { return; @@ -182,17 +209,32 @@ public final class BackgroundBlurDrawable extends Drawable { return PixelFormat.TRANSLUCENT; } + @Override + public String toString() { + return "BackgroundBlurDrawable{" + + "blurRadius=" + mBlurRadius + + ", corners={" + mCornerRadiusTL + + "," + mCornerRadiusTR + + "," + mCornerRadiusBL + + "," + mCornerRadiusBR + + "}, alpha=" + mAlpha + + ", visible=" + mVisible + + "}"; + } + /** * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a * message when it's time to propagate them. */ public static final class Aggregator { - - private final ArrayMap<BackgroundBlurDrawable, SurfaceControl.BlurRegion> mBlurRegions = - new ArrayMap<>(); + private final Object mRtLock = new Object(); + // Maintains a list of all *visible* blur drawables. Confined to UI thread + private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet(); + @GuardedBy("mRtLock") + private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray(); private final ViewRootImpl mViewRoot; - private float[][] mTmpBlurRegionsArray; - private boolean mNeedsUpdate; + private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0]; + private boolean mHasUiUpdates; public Aggregator(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -209,60 +251,191 @@ public final class BackgroundBlurDrawable extends Drawable { } /** - * Called from RenderThread only, already locked. - * @param drawable - * @param blurRegion + * Called when a BackgroundBlurDrawable has been updated */ - void onBlurRegionUpdated(BackgroundBlurDrawable drawable, - SurfaceControl.BlurRegion blurRegion) { - if (blurRegion.rect.isEmpty() || blurRegion.alpha == 0 || blurRegion.blurRadius == 0 - || !blurRegion.visible) { - mBlurRegions.remove(drawable); - mNeedsUpdate = true; - if (DEBUG) { - Log.d(TAG, "Remove " + blurRegion); + @UiThread + void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) { + final boolean shouldBeDrawn = + drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible; + final boolean isDrawn = mDrawables.contains(drawable); + if (shouldBeDrawn) { + mHasUiUpdates = true; + if (!isDrawn) { + mDrawables.add(drawable); + if (DEBUG) { + Log.d(TAG, "Add " + drawable); + } + } else { + if (DEBUG) { + Log.d(TAG, "Update " + drawable); + } } - } else { - mBlurRegions.put(drawable, blurRegion); - mNeedsUpdate = true; + } else if (!shouldBeDrawn && isDrawn) { + mHasUiUpdates = true; + mDrawables.remove(drawable); if (DEBUG) { - Log.d(TAG, "Update " + blurRegion); + Log.d(TAG, "Remove " + drawable); } } } + // Called from a thread pool + void onRenderNodePositionChanged(long frameNumber, Runnable update) { + // One of the blur region's position has changed, so we have to send an updated list + // of blur regions to SurfaceFlinger for this frame. + synchronized (mRtLock) { + ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber); + if (frameRtUpdates == null) { + frameRtUpdates = new ArraySet<>(); + mFrameRtUpdates.put(frameNumber, frameRtUpdates); + } + frameRtUpdates.add(update); + } + } + + /** + * @return true if there are any updates that need to be sent to SF + */ + @UiThread + public boolean hasUpdates() { + return mHasUiUpdates; + } + /** - * If there are any blur regions visible on the screen at the moment. + * @return true if there are any visible blur regions */ + @UiThread public boolean hasRegions() { - return mBlurRegions.size() > 0; + return mDrawables.size() > 0; + } + + /** + * @return an array of BlurRegions, which are holding a copy of the information in + * all the currently visible BackgroundBlurDrawables + */ + @UiThread + public BlurRegion[] getBlurRegionsCopyForRT() { + if (mHasUiUpdates) { + mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()]; + for (int i = 0; i < mDrawables.size(); i++) { + mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i)); + } + mHasUiUpdates = false; + } + + return mTmpBlurRegionsForFrame; } /** - * Dispatch blur updates, if there were any. - * @param frameNumber Frame where the update should happen. + * Called on RenderThread. + * + * @return all blur regions if there are any ui or position updates for this frame, + * null otherwise */ - public void dispatchBlurTransactionIfNeeded(long frameNumber) { - synchronized (this) { - if (!mNeedsUpdate) { - return; + @VisibleForTesting + public float[][] getBlurRegionsToDispatchToSf(long frameNumber, + BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) { + synchronized (mRtLock) { + if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0 + || mFrameRtUpdates.keyAt(0) > frameNumber)) { + return null; } - mNeedsUpdate = false; - if (mTmpBlurRegionsArray == null - || mTmpBlurRegionsArray.length != mBlurRegions.size()) { - mTmpBlurRegionsArray = new float[mBlurRegions.size()][]; + // mFrameRtUpdates holds position updates coming from a thread pool span from + // RenderThread. At this point, all position updates for frame frameNumber should + // have been added to mFrameRtUpdates. + // Here, we apply all updates for frames <= frameNumber in case some previous update + // has been missed. This also protects mFrameRtUpdates from memory leaks. + while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) { + final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0); + mFrameRtUpdates.removeAt(0); + for (int i = 0; i < frameUpdates.size(); i++) { + frameUpdates.valueAt(i).run(); + } } + } + + if (DEBUG) { + Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:"); + } + + final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][]; + for (int i = 0; i < blurRegionsArray.length; i++) { + blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray(); if (DEBUG) { - Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length - + " regions for frame " + frameNumber); - } - for (int i = 0; i < mTmpBlurRegionsArray.length; i++) { - mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray(); + Log.d(TAG, blurRegionsForFrame[i].toString()); } + } + return blurRegionsArray; + } - mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber); + /** + * Called on RenderThread in FrameDrawingCallback. + * Dispatch all blur regions if there are any ui or position updates. + */ + public void dispatchBlurTransactionIfNeeded(long frameNumber, + BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) { + final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber, + blurRegionsForFrame, hasUiUpdatesForFrame); + if (blurRegionsArray != null) { + mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber); } } + + } + + /** + * Wrapper for sending blur data to SurfaceFlinger + * Confined to RenderThread. + */ + public static final class BlurRegion { + public final int blurRadius; + public final float cornerRadiusTL; + public final float cornerRadiusTR; + public final float cornerRadiusBL; + public final float cornerRadiusBR; + public final float alpha; + public final Rect rect; + + BlurRegion(BackgroundBlurDrawable drawable) { + alpha = drawable.mAlpha; + blurRadius = drawable.mBlurRadius; + cornerRadiusTL = drawable.mCornerRadiusTL; + cornerRadiusTR = drawable.mCornerRadiusTR; + cornerRadiusBL = drawable.mCornerRadiusBL; + cornerRadiusBR = drawable.mCornerRadiusBR; + rect = drawable.mRect; + } + + /** + * Serializes this class into a float array that's more JNI friendly. + */ + float[] toFloatArray() { + final float[] floatArray = new float[10]; + floatArray[0] = blurRadius; + floatArray[1] = alpha; + floatArray[2] = rect.left; + floatArray[3] = rect.top; + floatArray[4] = rect.right; + floatArray[5] = rect.bottom; + floatArray[6] = cornerRadiusTL; + floatArray[7] = cornerRadiusTR; + floatArray[8] = cornerRadiusBL; + floatArray[9] = cornerRadiusBR; + return floatArray; + } + + @Override + public String toString() { + return "BlurRegion{" + + "blurRadius=" + blurRadius + + ", corners={" + cornerRadiusTL + + "," + cornerRadiusTR + + "," + cornerRadiusBL + + "," + cornerRadiusBR + + "}, alpha=" + alpha + + ", rect=" + rect + + "}"; + } } } diff --git a/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java b/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java new file mode 100644 index 000000000000..de6bf2044dbc --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java @@ -0,0 +1,50 @@ +/* + * 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.graphics.palette; + +import java.util.List; + +/** + * An implementation of Celebi's WSM quantizer, or, a Kmeans quantizer that starts with centroids + * from a Wu quantizer to ensure 100% reproducible and quality results, and has some optimizations + * to the Kmeans algorithm. + * + * See Celebi 2011, “Improving the Performance of K-Means for Color Quantization” + */ +public class CelebiQuantizer implements Quantizer { + private List<Palette.Swatch> mSwatches; + + public CelebiQuantizer() { } + + @Override + public void quantize(int[] pixels, int maxColors) { + WuQuantizer wu = new WuQuantizer(pixels, maxColors); + wu.quantize(pixels, maxColors); + List<Palette.Swatch> wuSwatches = wu.getQuantizedColors(); + LABCentroid labCentroidProvider = new LABCentroid(); + WSMeansQuantizer kmeans = + new WSMeansQuantizer(WSMeansQuantizer.createStartingCentroids(labCentroidProvider, + wuSwatches), labCentroidProvider, pixels, maxColors); + kmeans.quantize(pixels, maxColors); + mSwatches = kmeans.getQuantizedColors(); + } + + @Override + public List<Palette.Swatch> getQuantizedColors() { + return mSwatches; + } +} diff --git a/core/java/android/uwb/AngleOfArrivalSupport.aidl b/core/java/com/android/internal/graphics/palette/CentroidProvider.java index 57666ff8bca9..5fcfcbab3159 100644 --- a/core/java/android/uwb/AngleOfArrivalSupport.aidl +++ b/core/java/com/android/internal/graphics/palette/CentroidProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 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,31 +14,25 @@ * limitations under the License. */ -package android.uwb; +package com.android.internal.graphics.palette; -/** - * @hide - */ -@Backing(type="int") -enum AngleOfArrivalSupport { - /** - * The device does not support angle of arrival - */ - NONE, +import android.annotation.ColorInt; - /** - * The device supports planar angle of arrival - */ - TWO_DIMENSIONAL, +interface CentroidProvider { + /** + * @return 3 dimensions representing the color + */ + float[] getCentroid(@ColorInt int color); - /** - * The device does supports three dimensional angle of arrival with hemispherical azimuth angles - */ - THREE_DIMENSIONAL_HEMISPHERICAL, + /** + * @param centroid 3 dimensions representing the color + * @return 32-bit ARGB representation + */ + @ColorInt + int getColor(float[] centroid); - /** - * The device does supports three dimensional angle of arrival with full azimuth angles - */ - THREE_DIMENSIONAL_SPHERICAL, + /** + * Distance between two centroids. + */ + float distance(float[] a, float[] b); } - diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java index 9ac753b6d6ce..777949434c36 100644 --- a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java @@ -35,6 +35,8 @@ package com.android.internal.graphics.palette; import android.graphics.Color; import android.util.TimingLogger; +import com.android.internal.graphics.palette.Palette.Swatch; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -42,9 +44,6 @@ import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; -import com.android.internal.graphics.ColorUtils; -import com.android.internal.graphics.palette.Palette.Swatch; - /** * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/ * graphics/ColorCutQuantizer.java @@ -77,20 +76,17 @@ final class ColorCutQuantizer implements Quantizer { int[] mHistogram; List<Swatch> mQuantizedColors; TimingLogger mTimingLogger; - Palette.Filter[] mFilters; private final float[] mTempHsl = new float[3]; /** * Execute color quantization. * - * @param pixels histogram representing an image's pixel data + * @param pixels histogram representing an image's pixel data * @param maxColors The maximum number of colors that should be in the result palette. - * @param filters Set of filters to use in the quantization stage */ - public void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { + public void quantize(final int[] pixels, final int maxColors) { mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null; - mFilters = filters; final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)]; for (int i = 0; i < pixels.length; i++) { @@ -108,10 +104,6 @@ final class ColorCutQuantizer implements Quantizer { // Now let's count the number of distinct colors int distinctColorCount = 0; for (int color = 0; color < hist.length; color++) { - if (hist[color] > 0 && shouldIgnoreColor(color)) { - // If we should ignore the color, set the population to 0 - hist[color] = 0; - } if (hist[color] > 0) { // If the color has population, increase the distinct color count distinctColorCount++; @@ -186,7 +178,7 @@ final class ColorCutQuantizer implements Quantizer { * and splitting them. Once split, the new box and the remaining box are offered back to the * queue. * - * @param queue {@link java.util.PriorityQueue} to poll for boxes + * @param queue {@link java.util.PriorityQueue} to poll for boxes * @param maxSize Maximum amount of boxes to split */ private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) { @@ -216,11 +208,7 @@ final class ColorCutQuantizer implements Quantizer { ArrayList<Swatch> colors = new ArrayList<>(vboxes.size()); for (Vbox vbox : vboxes) { Swatch swatch = vbox.getAverageColor(); - if (!shouldIgnoreColor(swatch)) { - // As we're averaging a color box, we can still get colors which we do not want, so - // we check again here - colors.add(swatch); - } + colors.add(swatch); } return colors; } @@ -230,7 +218,7 @@ final class ColorCutQuantizer implements Quantizer { */ private class Vbox { // lower and upper index are inclusive - private int mLowerIndex; + private final int mLowerIndex; private int mUpperIndex; // Population of colors within this box private int mPopulation; @@ -373,7 +361,7 @@ final class ColorCutQuantizer implements Quantizer { modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex); final int midPoint = mPopulation / 2; - for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) { + for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) { count += hist[colors[i]]; if (count >= midPoint) { // we never want to split on the upperIndex, as this will result in the same @@ -447,27 +435,6 @@ final class ColorCutQuantizer implements Quantizer { } } - private boolean shouldIgnoreColor(int color565) { - final int rgb = approximateToRgb888(color565); - ColorUtils.colorToHSL(rgb, mTempHsl); - return shouldIgnoreColor(rgb, mTempHsl); - } - - private boolean shouldIgnoreColor(Swatch color) { - return shouldIgnoreColor(color.getRgb(), color.getHsl()); - } - - private boolean shouldIgnoreColor(int rgb, float[] hsl) { - if (mFilters != null && mFilters.length > 0) { - for (int i = 0, count = mFilters.length; i < count; i++) { - if (!mFilters[i].isAllowed(rgb, hsl)) { - return true; - } - } - } - return false; - } - /** * Comparator which sorts {@link Vbox} instances based on their volume, in descending order */ @@ -498,7 +465,8 @@ final class ColorCutQuantizer implements Quantizer { } private static int approximateToRgb888(int color) { - return approximateToRgb888(quantizedRed(color), quantizedGreen(color), quantizedBlue(color)); + return approximateToRgb888(quantizedRed(color), quantizedGreen(color), + quantizedBlue(color)); } /** diff --git a/core/java/com/android/internal/graphics/palette/Contrast.java b/core/java/com/android/internal/graphics/palette/Contrast.java new file mode 100644 index 000000000000..3dd1b8d8117c --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/Contrast.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.internal.graphics.palette; + +/** + * Helper methods for determining contrast between two colors, either via the colors themselves + * or components in different color spaces. + */ +public class Contrast { + /** + * + * @param y Y in XYZ that contrasts with the returned Y value + * @param contrast contrast ratio between color argument and returned Y value. Must be >= 1 + * or an exception will be thrown + * @return the lower Y coordinate in XYZ space that contrasts with color, or -1 if reaching + * no Y coordinate reaches contrast with color. + */ + public static float lighterY(float y, float contrast) { + assert (contrast >= 1); + float answer = -5 + contrast * (5 + y); + if (answer > 100.0) { + return -1; + } + return answer; + } + + + /** + * @param y Y in XYZ that contrasts with the returned Y value + * @param contrast contrast ratio between color argument and returned Y value. Must be >= 1 + * or an exception will be thrown + * @return the lower Y coordinate in XYZ space that contrasts with color, or -1 if reaching + * no Y coordinate reaches contrast with color. + */ + public static float darkerY(float y, float contrast) { + assert (contrast >= 1); + float answer = (5 - 5 * contrast + y) / contrast; + if (answer < 0.0) { + return -1; + } + return answer; + } + + /** + * Convert L* in L*a*b* to Y in XYZ. + * + * @param lstar L* in L*a*b* + * @return Y in XYZ + */ + public static float lstarToY(float lstar) { + // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html + float ke = 8.0f; + if (lstar > ke) { + return (float) (Math.pow(((lstar + 16.0) / 116.0), 3) * 100.0); + } else { + return (float) (lstar / (24389 / 27) * 100.0); + } + } + + /** + * Convert Y in XYZ to L* in L*a*b*. + * + * @param y Y in XYZ + * @return L* in L*a*b* + */ + public static float yToLstar(float y) { + y = y / 100.0f; + float e = 216.0f / 24389.0f; + float y_intermediate; + if (y <= e) { + y_intermediate = (24389.f / 27.f) * y; + // If y < e, can skip consecutive steps of / 116 + 16 followed by * 116 - 16. + return y_intermediate; + } else { + y_intermediate = (float) Math.cbrt(y); + } + return 116.f * y_intermediate - 16.f; + } + + + /** + * @return Contrast ratio between two Y values in XYZ space. + */ + public static float contrastYs(float y1, float y2) { + final float lighter = Math.max(y1, y2); + final float darker = (lighter == y1) ? y2 : y1; + return (lighter + 5) / (darker + 5); + } +} diff --git a/core/java/com/android/internal/graphics/palette/LABCentroid.java b/core/java/com/android/internal/graphics/palette/LABCentroid.java new file mode 100644 index 000000000000..98d5d2684857 --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/LABCentroid.java @@ -0,0 +1,67 @@ +/* + * 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.graphics.palette; + +import android.graphics.Color; +import android.graphics.ColorSpace; + +/** + * Allows quantizers to operate in the L*a*b* colorspace. + * L*a*b* is a good choice for measuring distance between colors. + * Better spaces, and better distance calculations even in L*a*b* exist, but measuring distance + * in L*a*b* space, also known as deltaE, is a universally accepted standard across industries + * and worldwide. + */ +public class LABCentroid implements CentroidProvider { + final ColorSpace.Connector mRgbToLab; + final ColorSpace.Connector mLabToRgb; + + public LABCentroid() { + mRgbToLab = ColorSpace.connect( + ColorSpace.get(ColorSpace.Named.SRGB), + ColorSpace.get(ColorSpace.Named.CIE_LAB)); + mLabToRgb = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.CIE_LAB), + ColorSpace.get(ColorSpace.Named.SRGB)); + } + + @Override + public float[] getCentroid(int color) { + float r = Color.red(color) / 255.f; + float g = Color.green(color) / 255.f; + float b = Color.blue(color) / 255.f; + + float[] transform = mRgbToLab.transform(r, g, b); + return transform; + } + + @Override + public int getColor(float[] centroid) { + float[] rgb = mLabToRgb.transform(centroid); + int color = Color.rgb(rgb[0], rgb[1], rgb[2]); + return color; + } + + @Override + public float distance(float[] a, float[] b) { + // Standard v1 CIELAB deltaE formula, 1976 - easily improved upon, however, + // improvements do not significantly impact the Palette algorithm's results. + double dL = a[0] - b[0]; + double dA = a[1] - b[1]; + double dB = a[2] - b[2]; + return (float) (Math.pow(dL, 2) + Math.pow(dA, 2) + Math.pow(dB, 2)); + } +} diff --git a/core/java/com/android/internal/graphics/palette/Mean.java b/core/java/com/android/internal/graphics/palette/Mean.java new file mode 100644 index 000000000000..894f91b6261c --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/Mean.java @@ -0,0 +1,45 @@ +/* + * 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.graphics.palette; + +import java.util.Random; + +/** + * Represents a centroid in Kmeans algorithms. + */ +public class Mean { + private static final Random RANDOM = new Random(0); + + public float[] center; + + /** + * Constructor. + * + * @param upperBound maximum value of a dimension in the space Kmeans is optimizing in + */ + Mean(int upperBound) { + center = + new float[]{ + RANDOM.nextInt(upperBound + 1), RANDOM.nextInt(upperBound + 1), + RANDOM.nextInt(upperBound + 1) + }; + } + + Mean(float[] center) { + this.center = center; + } +} diff --git a/core/java/com/android/internal/graphics/palette/MeanBucket.java b/core/java/com/android/internal/graphics/palette/MeanBucket.java new file mode 100644 index 000000000000..ae8858a8107c --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/MeanBucket.java @@ -0,0 +1,42 @@ +/* + * 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.graphics.palette; + +import java.util.HashSet; +import java.util.Set; + +class MeanBucket { + float[] mTotal = {0.f, 0.f, 0.f}; + int mCount = 0; + Set<Integer> mColors = new HashSet<>(); + + void add(float[] colorAsDoubles, int color, int colorCount) { + assert (colorAsDoubles.length == 3); + mColors.add(color); + mTotal[0] += (colorAsDoubles[0] * colorCount); + mTotal[1] += (colorAsDoubles[1] * colorCount); + mTotal[2] += (colorAsDoubles[2] * colorCount); + mCount += colorCount; + } + + float[] getCentroid() { + if (mCount == 0) { + return null; + } + return new float[]{mTotal[0] / mCount, mTotal[1] / mCount, mTotal[2] / mCount}; + } +} diff --git a/core/java/com/android/internal/graphics/palette/Palette.java b/core/java/com/android/internal/graphics/palette/Palette.java index a4f9a596050c..8b1137d7de7c 100644 --- a/core/java/com/android/internal/graphics/palette/Palette.java +++ b/core/java/com/android/internal/graphics/palette/Palette.java @@ -19,48 +19,24 @@ package com.android.internal.graphics.palette; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; -import android.os.AsyncTask; -import android.util.ArrayMap; import android.util.Log; -import android.util.SparseBooleanArray; -import android.util.TimingLogger; -import com.android.internal.graphics.ColorUtils; - -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; /** - * Copied from: /frameworks/support/v7/palette/src/main/java/android/support/v7/ - * graphics/Palette.java - * * A helper class to extract prominent colors from an image. - * <p> - * A number of colors with different profiles are extracted from the image: - * <ul> - * <li>Vibrant</li> - * <li>Vibrant Dark</li> - * <li>Vibrant Light</li> - * <li>Muted</li> - * <li>Muted Dark</li> - * <li>Muted Light</li> - * </ul> - * These can be retrieved from the appropriate getter method. * - * <p> - * Instances are created with a {@link Palette.Builder} which supports several options to tweak the + * <p>Instances are created with a {@link Builder} which supports several options to tweak the * generated Palette. See that class' documentation for more information. - * <p> - * Generation should always be completed on a background thread, ideally the one in - * which you load your image on. {@link Palette.Builder} supports both synchronous and asynchronous - * generation: + * + * <p>Generation should always be completed on a background thread, ideally the one in which you + * load your image on. {@link Builder} supports both synchronous and asynchronous generation: * * <pre> * // Synchronous @@ -85,346 +61,59 @@ public final class Palette { /** * Called when the {@link Palette} has been generated. */ - void onGenerated(Palette palette); + void onGenerated(@Nullable Palette palette); } static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112; static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16; - - static final float MIN_CONTRAST_TITLE_TEXT = 3.0f; - static final float MIN_CONTRAST_BODY_TEXT = 4.5f; - static final String LOG_TAG = "Palette"; - static final boolean LOG_TIMINGS = false; - /** - * Start generating a {@link Palette} with the returned {@link Palette.Builder} instance. - */ - public static Palette.Builder from(Bitmap bitmap) { - return new Palette.Builder(bitmap); + /** Start generating a {@link Palette} with the returned {@link Builder} instance. */ + @NonNull + public static Builder from(@NonNull Bitmap bitmap, @NonNull Quantizer quantizer) { + return new Builder(bitmap, quantizer); } /** * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches. - * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a - * list of swatches. Will return null if the {@code swatches} is null. - */ - public static Palette from(List<Palette.Swatch> swatches) { - return new Palette.Builder(swatches).generate(); - } - - /** - * @deprecated Use {@link Palette.Builder} to generate the Palette. - */ - @Deprecated - public static Palette generate(Bitmap bitmap) { - return from(bitmap).generate(); - } - - /** - * @deprecated Use {@link Palette.Builder} to generate the Palette. - */ - @Deprecated - public static Palette generate(Bitmap bitmap, int numColors) { - return from(bitmap).maximumColorCount(numColors).generate(); - } - - /** - * @deprecated Use {@link Palette.Builder} to generate the Palette. + * This + * is useful for testing, or if you want to resurrect a {@link Palette} instance from a list of + * swatches. Will return null if the {@code swatches} is null. */ - @Deprecated - public static AsyncTask<Bitmap, Void, Palette> generateAsync( - Bitmap bitmap, Palette.PaletteAsyncListener listener) { - return from(bitmap).generate(listener); - } - - /** - * @deprecated Use {@link Palette.Builder} to generate the Palette. - */ - @Deprecated - public static AsyncTask<Bitmap, Void, Palette> generateAsync( - final Bitmap bitmap, final int numColors, final Palette.PaletteAsyncListener listener) { - return from(bitmap).maximumColorCount(numColors).generate(listener); + @NonNull + public static Palette from(@NonNull List<Swatch> swatches) { + return new Builder(swatches).generate(); } - private final List<Palette.Swatch> mSwatches; - private final List<Target> mTargets; + private final List<Swatch> mSwatches; - private final Map<Target, Palette.Swatch> mSelectedSwatches; - private final SparseBooleanArray mUsedColors; - private final Palette.Swatch mDominantSwatch; + @Nullable + private final Swatch mDominantSwatch; - Palette(List<Palette.Swatch> swatches, List<Target> targets) { + Palette(List<Swatch> swatches) { mSwatches = swatches; - mTargets = targets; - - mUsedColors = new SparseBooleanArray(); - mSelectedSwatches = new ArrayMap<>(); - mDominantSwatch = findDominantSwatch(); } - /** - * Returns all of the swatches which make up the palette. - */ + /** Returns all of the swatches which make up the palette. */ @NonNull - public List<Palette.Swatch> getSwatches() { + public List<Swatch> getSwatches() { return Collections.unmodifiableList(mSwatches); } - /** - * Returns the targets used to generate this palette. - */ - @NonNull - public List<Target> getTargets() { - return Collections.unmodifiableList(mTargets); - } - - /** - * Returns the most vibrant swatch in the palette. Might be null. - * - * @see Target#VIBRANT - */ - @Nullable - public Palette.Swatch getVibrantSwatch() { - return getSwatchForTarget(Target.VIBRANT); - } - - /** - * Returns a light and vibrant swatch from the palette. Might be null. - * - * @see Target#LIGHT_VIBRANT - */ - @Nullable - public Palette.Swatch getLightVibrantSwatch() { - return getSwatchForTarget(Target.LIGHT_VIBRANT); - } - - /** - * Returns a dark and vibrant swatch from the palette. Might be null. - * - * @see Target#DARK_VIBRANT - */ - @Nullable - public Palette.Swatch getDarkVibrantSwatch() { - return getSwatchForTarget(Target.DARK_VIBRANT); - } - - /** - * Returns a muted swatch from the palette. Might be null. - * - * @see Target#MUTED - */ - @Nullable - public Palette.Swatch getMutedSwatch() { - return getSwatchForTarget(Target.MUTED); - } - - /** - * Returns a muted and light swatch from the palette. Might be null. - * - * @see Target#LIGHT_MUTED - */ - @Nullable - public Palette.Swatch getLightMutedSwatch() { - return getSwatchForTarget(Target.LIGHT_MUTED); - } - - /** - * Returns a muted and dark swatch from the palette. Might be null. - * - * @see Target#DARK_MUTED - */ + /** Returns the swatch with the highest population, or null if there are no swatches. */ @Nullable - public Palette.Swatch getDarkMutedSwatch() { - return getSwatchForTarget(Target.DARK_MUTED); - } - - /** - * Returns the most vibrant color in the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getVibrantSwatch() - */ - @ColorInt - public int getVibrantColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.VIBRANT, defaultColor); - } - - /** - * Returns a light and vibrant color from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getLightVibrantSwatch() - */ - @ColorInt - public int getLightVibrantColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor); - } - - /** - * Returns a dark and vibrant color from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getDarkVibrantSwatch() - */ - @ColorInt - public int getDarkVibrantColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.DARK_VIBRANT, defaultColor); - } - - /** - * Returns a muted color from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getMutedSwatch() - */ - @ColorInt - public int getMutedColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.MUTED, defaultColor); - } - - /** - * Returns a muted and light color from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getLightMutedSwatch() - */ - @ColorInt - public int getLightMutedColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.LIGHT_MUTED, defaultColor); - } - - /** - * Returns a muted and dark color from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getDarkMutedSwatch() - */ - @ColorInt - public int getDarkMutedColor(@ColorInt final int defaultColor) { - return getColorForTarget(Target.DARK_MUTED, defaultColor); - } - - /** - * Returns the selected swatch for the given target from the palette, or {@code null} if one - * could not be found. - */ - @Nullable - public Palette.Swatch getSwatchForTarget(@NonNull final Target target) { - return mSelectedSwatches.get(target); - } - - /** - * Returns the selected color for the given target from the palette as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - */ - @ColorInt - public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) { - Palette.Swatch swatch = getSwatchForTarget(target); - return swatch != null ? swatch.getRgb() : defaultColor; - } - - /** - * Returns the dominant swatch from the palette. - * - * <p>The dominant swatch is defined as the swatch with the greatest population (frequency) - * within the palette.</p> - */ - @Nullable - public Palette.Swatch getDominantSwatch() { + public Swatch getDominantSwatch() { return mDominantSwatch; } - /** - * Returns the color of the dominant swatch from the palette, as an RGB packed int. - * - * @param defaultColor value to return if the swatch isn't available - * @see #getDominantSwatch() - */ - @ColorInt - public int getDominantColor(@ColorInt int defaultColor) { - return mDominantSwatch != null ? mDominantSwatch.getRgb() : defaultColor; - } - - void generate() { - // We need to make sure that the scored targets are generated first. This is so that - // inherited targets have something to inherit from - for (int i = 0, count = mTargets.size(); i < count; i++) { - final Target target = mTargets.get(i); - target.normalizeWeights(); - mSelectedSwatches.put(target, generateScoredTarget(target)); - } - // We now clear out the used colors - mUsedColors.clear(); - } - - private Palette.Swatch generateScoredTarget(final Target target) { - final Palette.Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target); - if (maxScoreSwatch != null && target.isExclusive()) { - // If we have a swatch, and the target is exclusive, add the color to the used list - mUsedColors.append(maxScoreSwatch.getRgb(), true); - } - return maxScoreSwatch; - } - - private Palette.Swatch getMaxScoredSwatchForTarget(final Target target) { - float maxScore = 0; - Palette.Swatch maxScoreSwatch = null; - for (int i = 0, count = mSwatches.size(); i < count; i++) { - final Palette.Swatch swatch = mSwatches.get(i); - if (shouldBeScoredForTarget(swatch, target)) { - final float score = generateScore(swatch, target); - if (maxScoreSwatch == null || score > maxScore) { - maxScoreSwatch = swatch; - maxScore = score; - } - } - } - return maxScoreSwatch; - } - - private boolean shouldBeScoredForTarget(final Palette.Swatch swatch, final Target target) { - // Check whether the HSL values are within the correct ranges, and this color hasn't - // been used yet. - final float hsl[] = swatch.getHsl(); - return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation() - && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness() - && !mUsedColors.get(swatch.getRgb()); - } - - private float generateScore(Palette.Swatch swatch, Target target) { - final float[] hsl = swatch.getHsl(); - - float saturationScore = 0; - float luminanceScore = 0; - float populationScore = 0; - - final int maxPopulation = mDominantSwatch != null ? mDominantSwatch.getPopulation() : 1; - - if (target.getSaturationWeight() > 0) { - saturationScore = target.getSaturationWeight() - * (1f - Math.abs(hsl[1] - target.getTargetSaturation())); - } - if (target.getLightnessWeight() > 0) { - luminanceScore = target.getLightnessWeight() - * (1f - Math.abs(hsl[2] - target.getTargetLightness())); - } - if (target.getPopulationWeight() > 0) { - populationScore = target.getPopulationWeight() - * (swatch.getPopulation() / (float) maxPopulation); - } - - return saturationScore + luminanceScore + populationScore; - } - - private Palette.Swatch findDominantSwatch() { + @Nullable + private Swatch findDominantSwatch() { int maxPop = Integer.MIN_VALUE; - Palette.Swatch maxSwatch = null; + Swatch maxSwatch = null; for (int i = 0, count = mSwatches.size(); i < count; i++) { - Palette.Swatch swatch = mSwatches.get(i); + Swatch swatch = mSwatches.get(i); if (swatch.getPopulation() > maxPop) { maxSwatch = swatch; maxPop = swatch.getPopulation(); @@ -433,148 +122,42 @@ public final class Palette { return maxSwatch; } - private static float[] copyHslValues(Palette.Swatch color) { - final float[] newHsl = new float[3]; - System.arraycopy(color.getHsl(), 0, newHsl, 0, 3); - return newHsl; - } - /** * Represents a color swatch generated from an image's palette. The RGB color can be retrieved - * by calling {@link #getRgb()}. + * by + * calling {@link #getInt()}. */ - public static final class Swatch { - private final int mRed, mGreen, mBlue; - private final int mRgb; + public static class Swatch { + private final Color mColor; private final int mPopulation; - private boolean mGeneratedTextColors; - private int mTitleTextColor; - private int mBodyTextColor; - - private float[] mHsl; - - public Swatch(@ColorInt int color, int population) { - mRed = Color.red(color); - mGreen = Color.green(color); - mBlue = Color.blue(color); - mRgb = color; - mPopulation = population; - } - Swatch(int red, int green, int blue, int population) { - mRed = red; - mGreen = green; - mBlue = blue; - mRgb = Color.rgb(red, green, blue); + public Swatch(@ColorInt int colorInt, int population) { + mColor = Color.valueOf(colorInt); mPopulation = population; } - Swatch(float[] hsl, int population) { - this(ColorUtils.HSLToColor(hsl), population); - mHsl = hsl; - } - - /** - * @return this swatch's RGB color value - */ + /** @return this swatch's RGB color value */ @ColorInt - public int getRgb() { - return mRgb; + public int getInt() { + return mColor.toArgb(); } - /** - * Return this swatch's HSL values. - * hsv[0] is Hue [0 .. 360) - * hsv[1] is Saturation [0...1] - * hsv[2] is Lightness [0...1] - */ - public float[] getHsl() { - if (mHsl == null) { - mHsl = new float[3]; - } - ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl); - return mHsl; - } - - /** - * @return the number of pixels represented by this swatch - */ + /** @return the number of pixels represented by this swatch */ public int getPopulation() { return mPopulation; } - /** - * Returns an appropriate color to use for any 'title' text which is displayed over this - * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast. - */ - @ColorInt - public int getTitleTextColor() { - ensureTextColorsGenerated(); - return mTitleTextColor; - } - - /** - * Returns an appropriate color to use for any 'body' text which is displayed over this - * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast. - */ - @ColorInt - public int getBodyTextColor() { - ensureTextColorsGenerated(); - return mBodyTextColor; - } - - private void ensureTextColorsGenerated() { - if (!mGeneratedTextColors) { - // First check white, as most colors will be dark - final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha( - Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT); - final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha( - Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT); - - if (lightBodyAlpha != -1 && lightTitleAlpha != -1) { - // If we found valid light values, use them and return - mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha); - mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha); - mGeneratedTextColors = true; - return; - } - - final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha( - Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT); - final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha( - Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT); - - if (darkBodyAlpha != -1 && darkTitleAlpha != -1) { - // If we found valid dark values, use them and return - mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); - mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); - mGeneratedTextColors = true; - return; - } - - // If we reach here then we can not find title and body values which use the same - // lightness, we need to use mismatched values - mBodyTextColor = lightBodyAlpha != -1 - ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha) - : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); - mTitleTextColor = lightTitleAlpha != -1 - ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha) - : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); - mGeneratedTextColors = true; - } - } - @Override public String toString() { return new StringBuilder(getClass().getSimpleName()) - .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']') - .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']') - .append(" [Population: ").append(mPopulation).append(']') - .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor())) + .append(" [") + .append(mColor) + .append(']') + .append(" [Population: ") + .append(mPopulation) .append(']') - .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor())) - .append(']').toString(); + .toString(); } @Override @@ -586,243 +169,168 @@ public final class Palette { return false; } - Palette.Swatch - swatch = (Palette.Swatch) o; - return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb; + Swatch swatch = (Swatch) o; + return mPopulation == swatch.mPopulation && mColor.toArgb() == swatch.mColor.toArgb(); } @Override public int hashCode() { - return 31 * mRgb + mPopulation; + return 31 * mColor.toArgb() + mPopulation; } } - /** - * Builder class for generating {@link Palette} instances. - */ - public static final class Builder { - private final List<Palette.Swatch> mSwatches; + /** Builder class for generating {@link Palette} instances. */ + public static class Builder { + @Nullable + private final List<Swatch> mSwatches; + @Nullable private final Bitmap mBitmap; + @Nullable + private Quantizer mQuantizer = new ColorCutQuantizer(); - private final List<Target> mTargets = new ArrayList<>(); private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS; private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA; private int mResizeMaxDimension = -1; - private final List<Palette.Filter> mFilters = new ArrayList<>(); + @Nullable private Rect mRegion; - private Quantizer mQuantizer; - - /** - * Construct a new {@link Palette.Builder} using a source {@link Bitmap} - */ - public Builder(Bitmap bitmap) { + /** Construct a new {@link Builder} using a source {@link Bitmap} */ + public Builder(@NonNull Bitmap bitmap, @NonNull Quantizer quantizer) { if (bitmap == null || bitmap.isRecycled()) { throw new IllegalArgumentException("Bitmap is not valid"); } - mFilters.add(DEFAULT_FILTER); - mBitmap = bitmap; mSwatches = null; - - // Add the default targets - mTargets.add(Target.LIGHT_VIBRANT); - mTargets.add(Target.VIBRANT); - mTargets.add(Target.DARK_VIBRANT); - mTargets.add(Target.LIGHT_MUTED); - mTargets.add(Target.MUTED); - mTargets.add(Target.DARK_MUTED); + mBitmap = bitmap; + mQuantizer = quantizer == null ? new ColorCutQuantizer() : quantizer; } /** - * Construct a new {@link Palette.Builder} using a list of {@link Palette.Swatch} instances. - * Typically only used for testing. + * Construct a new {@link Builder} using a list of {@link Swatch} instances. Typically only + * used + * for testing. */ - public Builder(List<Palette.Swatch> swatches) { + public Builder(@NonNull List<Swatch> swatches) { if (swatches == null || swatches.isEmpty()) { throw new IllegalArgumentException("List of Swatches is not valid"); } - mFilters.add(DEFAULT_FILTER); mSwatches = swatches; mBitmap = null; + mQuantizer = null; } /** - * Set the maximum number of colors to use in the quantization step when using a - * {@link android.graphics.Bitmap} as the source. - * <p> - * Good values for depend on the source image type. For landscapes, good values are in - * the range 10-16. For images which are largely made up of people's faces then this - * value should be increased to ~24. + * Set the maximum number of colors to use in the quantization step when using a {@link + * android.graphics.Bitmap} as the source. + * + * <p>Good values for depend on the source image type. For landscapes, good values are in + * the + * range 10-16. For images which are largely made up of people's faces then this value + * should be + * increased to ~24. */ @NonNull - public Palette.Builder maximumColorCount(int colors) { + public Builder maximumColorCount(int colors) { mMaxColors = colors; return this; } /** - * Set the resize value when using a {@link android.graphics.Bitmap} as the source. - * If the bitmap's largest dimension is greater than the value specified, then the bitmap - * will be resized so that its largest dimension matches {@code maxDimension}. If the - * bitmap is smaller or equal, the original is used as-is. - * - * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle - * abnormal aspect ratios more gracefully. + * Set the resize value when using a {@link android.graphics.Bitmap} as the source. If the + * bitmap's largest dimension is greater than the value specified, then the bitmap will be + * resized so that its largest dimension matches {@code maxDimension}. If the bitmap is + * smaller + * or equal, the original is used as-is. * * @param maxDimension the number of pixels that the max dimension should be scaled down to, - * or any value <= 0 to disable resizing. + * or + * any value <= 0 to disable resizing. + * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle + * abnormal + * aspect ratios more gracefully. */ @NonNull @Deprecated - public Palette.Builder resizeBitmapSize(final int maxDimension) { + public Builder resizeBitmapSize(int maxDimension) { mResizeMaxDimension = maxDimension; mResizeArea = -1; return this; } /** - * Set the resize value when using a {@link android.graphics.Bitmap} as the source. - * If the bitmap's area is greater than the value specified, then the bitmap - * will be resized so that its area matches {@code area}. If the - * bitmap is smaller or equal, the original is used as-is. - * <p> - * This value has a large effect on the processing time. The larger the resized image is, - * the greater time it will take to generate the palette. The smaller the image is, the - * more detail is lost in the resulting image and thus less precision for color selection. + * Set the resize value when using a {@link android.graphics.Bitmap} as the source. If the + * bitmap's area is greater than the value specified, then the bitmap will be resized so + * that + * its area matches {@code area}. If the bitmap is smaller or equal, the original is used + * as-is. + * + * <p>This value has a large effect on the processing time. The larger the resized image is, + * the + * greater time it will take to generate the palette. The smaller the image is, the more + * detail + * is lost in the resulting image and thus less precision for color selection. * * @param area the number of pixels that the intermediary scaled down Bitmap should cover, - * or any value <= 0 to disable resizing. + * or + * any value <= 0 to disable resizing. */ @NonNull - public Palette.Builder resizeBitmapArea(final int area) { + public Builder resizeBitmapArea(int area) { mResizeArea = area; mResizeMaxDimension = -1; return this; } /** - * Clear all added filters. This includes any default filters added automatically by - * {@link Palette}. - */ - @NonNull - public Palette.Builder clearFilters() { - mFilters.clear(); - return this; - } - - /** - * Add a filter to be able to have fine grained control over which colors are - * allowed in the resulting palette. - * - * @param filter filter to add. - */ - @NonNull - public Palette.Builder addFilter( - Palette.Filter filter) { - if (filter != null) { - mFilters.add(filter); - } - return this; - } - - /** - * Set a specific quantization algorithm. {@link ColorCutQuantizer} will - * be used if unspecified. - * - * @param quantizer Quantizer implementation. - */ - @NonNull - public Palette.Builder setQuantizer(Quantizer quantizer) { - mQuantizer = quantizer; - return this; - } - - /** * Set a region of the bitmap to be used exclusively when calculating the palette. - * <p>This only works when the original input is a {@link Bitmap}.</p> * - * @param left The left side of the rectangle used for the region. - * @param top The top of the rectangle used for the region. - * @param right The right side of the rectangle used for the region. + * <p>This only works when the original input is a {@link Bitmap}. + * + * @param left The left side of the rectangle used for the region. + * @param top The top of the rectangle used for the region. + * @param right The right side of the rectangle used for the region. * @param bottom The bottom of the rectangle used for the region. */ @NonNull - public Palette.Builder setRegion(int left, int top, int right, int bottom) { + public Builder setRegion(@Px int left, @Px int top, @Px int right, @Px int bottom) { if (mBitmap != null) { if (mRegion == null) mRegion = new Rect(); // Set the Rect to be initially the whole Bitmap mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); // Now just get the intersection with the region if (!mRegion.intersect(left, top, right, bottom)) { - throw new IllegalArgumentException("The given region must intersect with " - + "the Bitmap's dimensions."); + throw new IllegalArgumentException( + "The given region must intersect with " + "the Bitmap's dimensions."); } } return this; } - /** - * Clear any previously region set via {@link #setRegion(int, int, int, int)}. - */ + /** Clear any previously region set via {@link #setRegion(int, int, int, int)}. */ @NonNull - public Palette.Builder clearRegion() { + public Builder clearRegion() { mRegion = null; return this; } - /** - * Add a target profile to be generated in the palette. - * - * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p> - */ - @NonNull - public Palette.Builder addTarget(@NonNull final Target target) { - if (!mTargets.contains(target)) { - mTargets.add(target); - } - return this; - } - /** - * Clear all added targets. This includes any default targets added automatically by - * {@link Palette}. - */ - @NonNull - public Palette.Builder clearTargets() { - if (mTargets != null) { - mTargets.clear(); - } - return this; - } - - /** - * Generate and return the {@link Palette} synchronously. - */ + /** Generate and return the {@link Palette} synchronously. */ @NonNull public Palette generate() { - final TimingLogger logger = LOG_TIMINGS - ? new TimingLogger(LOG_TAG, "Generation") - : null; - - List<Palette.Swatch> swatches; + List<Swatch> swatches; if (mBitmap != null) { // We have a Bitmap so we need to use quantization to reduce the number of colors // First we'll scale down the bitmap if needed - final Bitmap bitmap = scaleBitmapDown(mBitmap); - - if (logger != null) { - logger.addSplit("Processed Bitmap"); - } + Bitmap bitmap = scaleBitmapDown(mBitmap); - final Rect region = mRegion; + Rect region = mRegion; if (bitmap != mBitmap && region != null) { // If we have a scaled bitmap and a selected region, we need to scale down the // region to match the new scale - final double scale = bitmap.getWidth() / (double) mBitmap.getWidth(); + double scale = bitmap.getWidth() / (double) mBitmap.getWidth(); region.left = (int) Math.floor(region.left * scale); region.top = (int) Math.floor(region.top * scale); region.right = Math.min((int) Math.ceil(region.right * scale), @@ -832,54 +340,47 @@ public final class Palette { } // Now generate a quantizer from the Bitmap - if (mQuantizer == null) { - mQuantizer = new ColorCutQuantizer(); - } - mQuantizer.quantize(getPixelsFromBitmap(bitmap), - mMaxColors, mFilters.isEmpty() ? null : - mFilters.toArray(new Palette.Filter[mFilters.size()])); + mQuantizer.quantize( + getPixelsFromBitmap(bitmap), + mMaxColors); // If created a new bitmap, recycle it if (bitmap != mBitmap) { bitmap.recycle(); } - swatches = mQuantizer.getQuantizedColors(); - - if (logger != null) { - logger.addSplit("Color quantization completed"); - } - } else { + } else if (mSwatches != null) { // Else we're using the provided swatches swatches = mSwatches; + } else { + // The constructors enforce either a bitmap or swatches are present. + throw new AssertionError(); } // Now create a Palette instance - final Palette p = new Palette(swatches, mTargets); + Palette p = new Palette(swatches); // And make it generate itself - p.generate(); - - if (logger != null) { - logger.addSplit("Created Palette"); - logger.dumpToLog(); - } return p; } /** - * Generate the {@link Palette} asynchronously. The provided listener's - * {@link Palette.PaletteAsyncListener#onGenerated} method will be called with the palette when - * generated. + * Generate the {@link Palette} asynchronously. The provided listener's {@link + * PaletteAsyncListener#onGenerated} method will be called with the palette when generated. + * + * @deprecated Use the standard <code>java.util.concurrent</code> or <a + * href="https://developer.android.com/topic/libraries/architecture/coroutines">Kotlin + * concurrency utilities</a> to call {@link #generate()} instead. */ @NonNull - public AsyncTask<Bitmap, Void, Palette> generate(final Palette.PaletteAsyncListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener can not be null"); - } + @Deprecated + public android.os.AsyncTask<Bitmap, Void, Palette> generate( + @NonNull PaletteAsyncListener listener) { + assert (listener != null); - return new AsyncTask<Bitmap, Void, Palette>() { + return new android.os.AsyncTask<Bitmap, Void, Palette>() { @Override + @Nullable protected Palette doInBackground(Bitmap... params) { try { return generate(); @@ -890,16 +391,16 @@ public final class Palette { } @Override - protected void onPostExecute(Palette colorExtractor) { + protected void onPostExecute(@Nullable Palette colorExtractor) { listener.onGenerated(colorExtractor); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap); + }.executeOnExecutor(android.os.AsyncTask.THREAD_POOL_EXECUTOR, mBitmap); } private int[] getPixelsFromBitmap(Bitmap bitmap) { - final int bitmapWidth = bitmap.getWidth(); - final int bitmapHeight = bitmap.getHeight(); - final int[] pixels = new int[bitmapWidth * bitmapHeight]; + int bitmapWidth = bitmap.getWidth(); + int bitmapHeight = bitmap.getHeight(); + int[] pixels = new int[bitmapWidth * bitmapHeight]; bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight); if (mRegion == null) { @@ -908,32 +409,34 @@ public final class Palette { } else { // If we do have a region, lets create a subset array containing only the region's // pixels - final int regionWidth = mRegion.width(); - final int regionHeight = mRegion.height(); + int regionWidth = mRegion.width(); + int regionHeight = mRegion.height(); // pixels contains all of the pixels, so we need to iterate through each row and // copy the regions pixels into a new smaller array - final int[] subsetPixels = new int[regionWidth * regionHeight]; + int[] subsetPixels = new int[regionWidth * regionHeight]; for (int row = 0; row < regionHeight; row++) { - System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left, - subsetPixels, row * regionWidth, regionWidth); + System.arraycopy( + pixels, + ((row + mRegion.top) * bitmapWidth) + mRegion.left, + subsetPixels, + row * regionWidth, + regionWidth); } return subsetPixels; } } - /** - * Scale the bitmap down as needed. - */ - private Bitmap scaleBitmapDown(final Bitmap bitmap) { + /** Scale the bitmap down as needed. */ + private Bitmap scaleBitmapDown(Bitmap bitmap) { double scaleRatio = -1; if (mResizeArea > 0) { - final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); + int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); if (bitmapArea > mResizeArea) { scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea); } } else if (mResizeMaxDimension > 0) { - final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); if (maxDimension > mResizeMaxDimension) { scaleRatio = mResizeMaxDimension / (double) maxDimension; } @@ -944,11 +447,13 @@ public final class Palette { return bitmap; } - return Bitmap.createScaledBitmap(bitmap, + return Bitmap.createScaledBitmap( + bitmap, (int) Math.ceil(bitmap.getWidth() * scaleRatio), (int) Math.ceil(bitmap.getHeight() * scaleRatio), false); } + } /** @@ -961,9 +466,7 @@ public final class Palette { * * @param rgb the color in RGB888. * @param hsl HSL representation of the color. - * * @return true if the color is allowed, false if not. - * * @see Palette.Builder#addFilter(Palette.Filter) */ boolean isAllowed(int rgb, float[] hsl); @@ -1004,3 +507,4 @@ public final class Palette { } }; } + diff --git a/core/java/com/android/internal/graphics/palette/Quantizer.java b/core/java/com/android/internal/graphics/palette/Quantizer.java index db60f2e9dc69..a219ea3aa7d0 100644 --- a/core/java/com/android/internal/graphics/palette/Quantizer.java +++ b/core/java/com/android/internal/graphics/palette/Quantizer.java @@ -22,6 +22,15 @@ import java.util.List; * Definition of an algorithm that receives pixels and outputs a list of colors. */ public interface Quantizer { - void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters); + /** + * Create colors representative of the colors present in pixels. + * @param pixels Set of ARGB representation of a color. + * @param maxColors number of colors to generate + */ + void quantize(int[] pixels, int maxColors); + + /** + * List of colors generated by previous call to quantize. + */ List<Palette.Swatch> getQuantizedColors(); } diff --git a/core/java/com/android/internal/graphics/palette/Target.java b/core/java/com/android/internal/graphics/palette/Target.java index 0540d80ef6f0..96e7faa81c3a 100644 --- a/core/java/com/android/internal/graphics/palette/Target.java +++ b/core/java/com/android/internal/graphics/palette/Target.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 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,368 +16,234 @@ package com.android.internal.graphics.palette; -/* - * Copyright 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; /** - * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/graphics/Target.java - * - * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances - * can be created via the {@link android.support.v7.graphics.Target.Builder} class. - * - * <p>To use the target, use the {@link Palette.Builder#addTarget(Target)} API when building a - * Palette.</p> + * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances can + * be created via the {@link Builder} class. */ -public final class Target { - - private static final float TARGET_DARK_LUMA = 0.26f; - private static final float MAX_DARK_LUMA = 0.45f; - - private static final float MIN_LIGHT_LUMA = 0.55f; - private static final float TARGET_LIGHT_LUMA = 0.74f; - - private static final float MIN_NORMAL_LUMA = 0.3f; - private static final float TARGET_NORMAL_LUMA = 0.5f; - private static final float MAX_NORMAL_LUMA = 0.7f; - - private static final float TARGET_MUTED_SATURATION = 0.3f; - private static final float MAX_MUTED_SATURATION = 0.4f; - - private static final float TARGET_VIBRANT_SATURATION = 1f; - private static final float MIN_VIBRANT_SATURATION = 0.35f; - - private static final float WEIGHT_SATURATION = 0.24f; - private static final float WEIGHT_LUMA = 0.52f; - private static final float WEIGHT_POPULATION = 0.24f; - - static final int INDEX_MIN = 0; - static final int INDEX_TARGET = 1; - static final int INDEX_MAX = 2; - - static final int INDEX_WEIGHT_SAT = 0; - static final int INDEX_WEIGHT_LUMA = 1; - static final int INDEX_WEIGHT_POP = 2; - - /** - * A target which has the characteristics of a vibrant color which is light in luminance. - */ - public static final Target LIGHT_VIBRANT; - - /** - * A target which has the characteristics of a vibrant color which is neither light or dark. - */ - public static final Target VIBRANT; - - /** - * A target which has the characteristics of a vibrant color which is dark in luminance. - */ - public static final Target DARK_VIBRANT; - - /** - * A target which has the characteristics of a muted color which is light in luminance. - */ - public static final Target LIGHT_MUTED; - - /** - * A target which has the characteristics of a muted color which is neither light or dark. - */ - public static final Target MUTED; - - /** - * A target which has the characteristics of a muted color which is dark in luminance. - */ - public static final Target DARK_MUTED; - static { - LIGHT_VIBRANT = new Target(); - setDefaultLightLightnessValues(LIGHT_VIBRANT); - setDefaultVibrantSaturationValues(LIGHT_VIBRANT); - - VIBRANT = new Target(); - setDefaultNormalLightnessValues(VIBRANT); - setDefaultVibrantSaturationValues(VIBRANT); - - DARK_VIBRANT = new Target(); - setDefaultDarkLightnessValues(DARK_VIBRANT); - setDefaultVibrantSaturationValues(DARK_VIBRANT); - - LIGHT_MUTED = new Target(); - setDefaultLightLightnessValues(LIGHT_MUTED); - setDefaultMutedSaturationValues(LIGHT_MUTED); - - MUTED = new Target(); - setDefaultNormalLightnessValues(MUTED); - setDefaultMutedSaturationValues(MUTED); - - DARK_MUTED = new Target(); - setDefaultDarkLightnessValues(DARK_MUTED); - setDefaultMutedSaturationValues(DARK_MUTED); - } - - final float[] mSaturationTargets = new float[3]; - final float[] mLightnessTargets = new float[3]; - final float[] mWeights = new float[3]; - boolean mIsExclusive = true; // default to true +public final class Target { + private static final float WEIGHT_CHROMA = 0.5f; + private static final float WEIGHT_RELATIVE_LUMINANCE = 0.5f; + private static final float WEIGHT_POPULATION = 0.3f; + private static final float WEIGHT_HUE = 0.2f; + + // Arbitrarily chosen, except max - CAM16 chroma has a ceiling of 130, based on unit testing. + private static final float DEFAULT_CHROMA_MIN = 0.f; + private static final float DEFAULT_CHROMA_MAX = 130.f; + private static final float DEFAULT_CHROMA_TARGET = 30.f; + + private float mTargetRelativeLuminance = -1.0f; + private float mChromaWeight; + private float mChromaTarget; + private float mChromaMin; + private float mChromaMax; + private float mRelativeLuminanceWeight; + private float mPopulationWeight; + private float mHueWeight; + private float mTargetHue; Target() { - setTargetDefaultValues(mSaturationTargets); - setTargetDefaultValues(mLightnessTargets); - setDefaultWeights(); + mChromaMax = DEFAULT_CHROMA_MAX; + mChromaMin = DEFAULT_CHROMA_MIN; + mChromaTarget = DEFAULT_CHROMA_TARGET; + mChromaWeight = WEIGHT_CHROMA; + mRelativeLuminanceWeight = WEIGHT_RELATIVE_LUMINANCE; + mPopulationWeight = WEIGHT_POPULATION; + mHueWeight = WEIGHT_HUE; } - Target(Target from) { - System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0, - mSaturationTargets.length); - System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0, - mLightnessTargets.length); - System.arraycopy(from.mWeights, 0, mWeights, 0, mWeights.length); + Target(@NonNull Target from) { + mTargetRelativeLuminance = from.mTargetRelativeLuminance; + mChromaWeight = from.mChromaWeight; + mRelativeLuminanceWeight = from.mRelativeLuminanceWeight; + mPopulationWeight = from.mPopulationWeight; + mHueWeight = from.mHueWeight; + mChromaTarget = from.mChromaTarget; + mChromaMin = from.mChromaMin; + mChromaMax = from.mChromaMax; } - /** - * The minimum saturation value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getMinimumSaturation() { - return mSaturationTargets[INDEX_MIN]; + /** The relative luminance value for this target. */ + @FloatRange(from = 0, to = 100) + public float getTargetRelativeLuminance() { + return mTargetRelativeLuminance; } - /** - * The target saturation value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getTargetSaturation() { - return mSaturationTargets[INDEX_TARGET]; + /** The relative luminance value for this target. */ + @FloatRange(from = 0, to = 100) + public float getTargetPerceptualLuminance() { + return Contrast.yToLstar(mTargetRelativeLuminance); } - /** - * The maximum saturation value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getMaximumSaturation() { - return mSaturationTargets[INDEX_MAX]; + /** The minimum chroma value for this target. */ + @FloatRange(from = 0, to = 100) + public float getMinimumChroma() { + return mChromaMin; } - /** - * The minimum lightness value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getMinimumLightness() { - return mLightnessTargets[INDEX_MIN]; + /** The target chroma value for this target. */ + @FloatRange(from = 0, to = 100) + public float getTargetChroma() { + return mChromaTarget; } - /** - * The target lightness value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getTargetLightness() { - return mLightnessTargets[INDEX_TARGET]; + /** The maximum chroma value for this target. */ + @FloatRange(from = 0, to = 130) + public float getMaximumChroma() { + return mChromaMax; } - /** - * The maximum lightness value for this target. - */ - @FloatRange(from = 0, to = 1) - public float getMaximumLightness() { - return mLightnessTargets[INDEX_MAX]; + /** The target hue value for this target. */ + @FloatRange(from = 0, to = 100) + public float getTargetHue() { + return mTargetHue; } /** - * Returns the weight of importance that this target places on a color's saturation within - * the image. + * Returns the weight of importance that this target places on a color's chroma within the + * image. * * <p>The larger the weight, relative to the other weights, the more important that a color - * being close to the target value has on selection.</p> + * being + * close to the target value has on selection. * - * @see #getTargetSaturation() + * @see #getTargetChroma() */ - public float getSaturationWeight() { - return mWeights[INDEX_WEIGHT_SAT]; + public float getChromaWeight() { + return mChromaWeight; } /** - * Returns the weight of importance that this target places on a color's lightness within - * the image. + * Returns the weight of importance that this target places on a color's lightness within the + * image. * * <p>The larger the weight, relative to the other weights, the more important that a color - * being close to the target value has on selection.</p> + * being + * close to the target value has on selection. * - * @see #getTargetLightness() + * @see #getTargetRelativeLuminance() */ public float getLightnessWeight() { - return mWeights[INDEX_WEIGHT_LUMA]; + return mRelativeLuminanceWeight; } /** - * Returns the weight of importance that this target places on a color's population within - * the image. + * Returns the weight of importance that this target places on a color's population within the + * image. * - * <p>The larger the weight, relative to the other weights, the more important that a - * color's population being close to the most populous has on selection.</p> + * <p>The larger the weight, relative to the other weights, the more important that a color's + * population being close to the most populous has on selection. */ public float getPopulationWeight() { - return mWeights[INDEX_WEIGHT_POP]; + return mPopulationWeight; } /** - * Returns whether any color selected for this target is exclusive for this target only. + * Returns the weight of importance that this target places on a color's hue. * - * <p>If false, then the color can be selected for other targets.</p> + * <p>The larger the weight, relative to the other weights, the more important that a color's + * hue being close to the desired hue has on selection. */ - public boolean isExclusive() { - return mIsExclusive; + public float getHueWeight() { + return mHueWeight; } - private static void setTargetDefaultValues(final float[] values) { - values[INDEX_MIN] = 0f; - values[INDEX_TARGET] = 0.5f; - values[INDEX_MAX] = 1f; - } - - private void setDefaultWeights() { - mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION; - mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA; - mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION; - } - void normalizeWeights() { - float sum = 0; - for (int i = 0, z = mWeights.length; i < z; i++) { - float weight = mWeights[i]; - if (weight > 0) { - sum += weight; - } - } - if (sum != 0) { - for (int i = 0, z = mWeights.length; i < z; i++) { - if (mWeights[i] > 0) { - mWeights[i] /= sum; - } - } - } - } - - private static void setDefaultDarkLightnessValues(Target target) { - target.mLightnessTargets[INDEX_TARGET] = TARGET_DARK_LUMA; - target.mLightnessTargets[INDEX_MAX] = MAX_DARK_LUMA; - } - - private static void setDefaultNormalLightnessValues(Target target) { - target.mLightnessTargets[INDEX_MIN] = MIN_NORMAL_LUMA; - target.mLightnessTargets[INDEX_TARGET] = TARGET_NORMAL_LUMA; - target.mLightnessTargets[INDEX_MAX] = MAX_NORMAL_LUMA; - } - - private static void setDefaultLightLightnessValues(Target target) { - target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA; - target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA; - } - - private static void setDefaultVibrantSaturationValues(Target target) { - target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION; - target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION; - } - - private static void setDefaultMutedSaturationValues(Target target) { - target.mSaturationTargets[INDEX_TARGET] = TARGET_MUTED_SATURATION; - target.mSaturationTargets[INDEX_MAX] = MAX_MUTED_SATURATION; - } - - /** - * Builder class for generating custom {@link Target} instances. - */ - public final static class Builder { + /** Builder class for generating custom {@link Target} instances. */ + public static class Builder { private final Target mTarget; - /** - * Create a new {@link Target} builder from scratch. - */ + /** Create a new {@link Target} builder from scratch. */ public Builder() { mTarget = new Target(); } - /** - * Create a new builder based on an existing {@link Target}. - */ - public Builder(Target target) { + /** Create a new builder based on an existing {@link Target}. */ + public Builder(@NonNull Target target) { mTarget = new Target(target); } - /** - * Set the minimum saturation value for this target. - */ - public Target.Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) { - mTarget.mSaturationTargets[INDEX_MIN] = value; + /** Set the minimum chroma value for this target. */ + @NonNull + public Builder setMinimumChroma(@FloatRange(from = 0, to = 100) float value) { + mTarget.mChromaMin = value; return this; } - /** - * Set the target/ideal saturation value for this target. - */ - public Target.Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) { - mTarget.mSaturationTargets[INDEX_TARGET] = value; + /** Set the target/ideal chroma value for this target. */ + @NonNull + public Builder setTargetChroma(@FloatRange(from = 0, to = 100) float value) { + mTarget.mChromaTarget = value; return this; } - /** - * Set the maximum saturation value for this target. - */ - public Target.Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) { - mTarget.mSaturationTargets[INDEX_MAX] = value; + /** Set the maximum chroma value for this target. */ + @NonNull + public Builder setMaximumChroma(@FloatRange(from = 0, to = 100) float value) { + mTarget.mChromaMax = value; return this; } - /** - * Set the minimum lightness value for this target. - */ - public Target.Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) { - mTarget.mLightnessTargets[INDEX_MIN] = value; + /** Set the minimum lightness value for this target, using Y in XYZ color space. */ + @NonNull + public Builder setTargetRelativeLuminance(@FloatRange(from = 0, to = 100) float value) { + mTarget.mTargetRelativeLuminance = value; return this; } - /** - * Set the target/ideal lightness value for this target. - */ - public Target.Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) { - mTarget.mLightnessTargets[INDEX_TARGET] = value; + /** Set the minimum lightness value for this target, using L* in LAB color space. */ + @NonNull + public Builder setTargetPerceptualLuminance(@FloatRange(from = 0, to = 100) float value) { + mTarget.mTargetRelativeLuminance = Contrast.lstarToY(value); return this; } /** - * Set the maximum lightness value for this target. + * Set the hue desired from the target. This hue is not enforced, the only consequence + * is points will be awarded to seed colors the closer they are to this hue. */ - public Target.Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) { - mTarget.mLightnessTargets[INDEX_MAX] = value; + @NonNull + public Builder setTargetHue(@IntRange(from = 0, to = 360) int hue) { + mTarget.mTargetHue = hue; + return this; + } + + /** Sets lightness value for this target. */ + @NonNull + public Builder setContrastRatio( + @FloatRange(from = 1, to = 21) float value, + @FloatRange(from = 0, to = 100) float relativeLuminance) { + float counterpartY = relativeLuminance; + float lstar = Contrast.yToLstar(counterpartY); + + float targetY; + if (lstar < 50) { + targetY = Contrast.lighterY(counterpartY, value); + } else { + targetY = Contrast.darkerY(counterpartY, value); + } + mTarget.mTargetRelativeLuminance = targetY; return this; } /** - * Set the weight of importance that this target will place on saturation values. + * Set the weight of importance that this target will place on chroma values. * * <p>The larger the weight, relative to the other weights, the more important that a color - * being close to the target value has on selection.</p> + * being close to the target value has on selection. * - * <p>A weight of 0 means that it has no weight, and thus has no - * bearing on the selection.</p> + * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection. * - * @see #setTargetSaturation(float) + * @see #setTargetChroma(float) */ - public Target.Builder setSaturationWeight(@FloatRange(from = 0) float weight) { - mTarget.mWeights[INDEX_WEIGHT_SAT] = weight; + @NonNull + public Builder setChromaWeight(@FloatRange(from = 0) float weight) { + mTarget.mChromaWeight = weight; return this; } @@ -385,51 +251,40 @@ public final class Target { * Set the weight of importance that this target will place on lightness values. * * <p>The larger the weight, relative to the other weights, the more important that a color - * being close to the target value has on selection.</p> + * being close to the target value has on selection. * - * <p>A weight of 0 means that it has no weight, and thus has no - * bearing on the selection.</p> + * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection. * - * @see #setTargetLightness(float) + * @see #setTargetRelativeLuminance(float) */ - public Target.Builder setLightnessWeight(@FloatRange(from = 0) float weight) { - mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight; + @NonNull + public Builder setLightnessWeight(@FloatRange(from = 0) float weight) { + mTarget.mRelativeLuminanceWeight = weight; return this; } /** * Set the weight of importance that this target will place on a color's population within - * the image. + * the + * image. * * <p>The larger the weight, relative to the other weights, the more important that a - * color's population being close to the most populous has on selection.</p> + * color's + * population being close to the most populous has on selection. * - * <p>A weight of 0 means that it has no weight, and thus has no - * bearing on the selection.</p> + * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection. */ - public Target.Builder setPopulationWeight(@FloatRange(from = 0) float weight) { - mTarget.mWeights[INDEX_WEIGHT_POP] = weight; + @NonNull + public Builder setPopulationWeight(@FloatRange(from = 0) float weight) { + mTarget.mPopulationWeight = weight; return this; } - /** - * Set whether any color selected for this target is exclusive to this target only. - * Defaults to true. - * - * @param exclusive true if any the color is exclusive to this target, or false is the - * color can be selected for other targets. - */ - public Target.Builder setExclusive(boolean exclusive) { - mTarget.mIsExclusive = exclusive; - return this; - } - /** - * Builds and returns the resulting {@link Target}. - */ + /** Builds and returns the resulting {@link Target}. */ + @NonNull public Target build() { return mTarget; } } - -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java index b0355350dc15..d791f7b3e6be 100644 --- a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java @@ -70,10 +70,9 @@ public class VariationalKMeansQuantizer implements Quantizer { * * @param pixels Pixels to quantize. * @param maxColors Maximum number of clusters to extract. - * @param filters Colors that should be ignored */ @Override - public void quantize(int[] pixels, int maxColors, Palette.Filter[] filters) { + public void quantize(int[] pixels, int maxColors) { // Start by converting all colors to HSL. // HLS is way more meaningful for clustering than RGB. final float[] hsl = {0, 0, 0}; @@ -111,16 +110,18 @@ public class VariationalKMeansQuantizer implements Quantizer { // Convert data to final format, de-normalizing the hue. mQuantizedColors = new ArrayList<>(); + float[] mHsl = new float[3]; for (KMeans.Mean mean : optimalMeans) { if (mean.getItems().size() == 0) { continue; } float[] centroid = mean.getCentroid(); - mQuantizedColors.add(new Palette.Swatch(new float[]{ - centroid[0] * 360f, - centroid[1], - centroid[2] - }, mean.getItems().size())); + + mHsl[0] = centroid[0] * 360f; + mHsl[1] = centroid[1]; + mHsl[2] = centroid[2]; + int color = ColorUtils.HSLToColor(mHsl); + mQuantizedColors.add(new Palette.Swatch(color, mean.getItems().size())); } } diff --git a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java new file mode 100644 index 000000000000..a87a34f4ae11 --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java @@ -0,0 +1,269 @@ +/* + * 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.graphics.palette; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * A color quantizer based on the Kmeans algorithm. + * + * This is an implementation of Kmeans based on Celebi's 2011 paper, + * "Improving the Performance of K-Means for Color Quantization". In the paper, this algorithm is + * referred to as "WSMeans", or, "Weighted Square Means" The main advantages of this Kmeans + * implementation are taking advantage of triangle properties to avoid distance calculations, as + * well as indexing colors by their count, thus minimizing the number of points to move around. + * + * Celebi's paper also stabilizes results and guarantees high quality by using starting centroids + * from Wu's quantization algorithm. See CelebiQuantizer for more info. + */ +public class WSMeansQuantizer implements Quantizer { + Mean[] mMeans; + private final Map<Integer, Integer> mCountByColor = new HashMap<>(); + private final Map<Integer, Integer> mMeanIndexByColor = new HashMap<>(); + private final Set<Integer> mUniqueColors = new HashSet<>(); + private final List<Palette.Swatch> mSwatches = new ArrayList<>(); + private final CentroidProvider mCentroidProvider; + + public WSMeansQuantizer( + float[][] means, CentroidProvider centroidProvider, int[] pixels, int maxColors) { + if (pixels == null) { + pixels = new int[]{}; + } + mCentroidProvider = centroidProvider; + mMeans = new Mean[maxColors]; + for (int i = 0; i < means.length; i++) { + mMeans[i] = new Mean(means[i]); + } + + if (maxColors > means.length) { + int randomMeansToCreate = maxColors - means.length; + for (int i = 0; i < randomMeansToCreate; i++) { + mMeans[means.length + i] = new Mean(100); + } + } + + for (int pixel : pixels) { + Integer currentCount = mCountByColor.get(pixel); + if (currentCount == null) { + currentCount = 0; + mUniqueColors.add(pixel); + } + mCountByColor.put(pixel, currentCount + 1); + } + for (int color : mUniqueColors) { + int closestMeanIndex = -1; + double closestMeanDistance = -1; + float[] centroid = mCentroidProvider.getCentroid(color); + for (int i = 0; i < mMeans.length; i++) { + double distance = mCentroidProvider.distance(centroid, mMeans[i].center); + if (closestMeanIndex == -1 || distance < closestMeanDistance) { + closestMeanIndex = i; + closestMeanDistance = distance; + } + } + mMeanIndexByColor.put(color, closestMeanIndex); + } + + if (pixels.length == 0) { + return; + } + + predict(maxColors, 0); + } + + /** Create starting centroids for K-means from a set of colors. */ + public static float[][] createStartingCentroids(CentroidProvider centroidProvider, + List<Palette.Swatch> swatches) { + float[][] startingCentroids = new float[swatches.size()][]; + for (int i = 0; i < swatches.size(); i++) { + startingCentroids[i] = centroidProvider.getCentroid(swatches.get(i).getInt()); + } + return startingCentroids; + } + + /** Create random starting centroids for K-means. */ + public static float[][] randomMeans(int maxColors, int upperBound) { + float[][] means = new float[maxColors][]; + for (int i = 0; i < maxColors; i++) { + means[i] = new Mean(upperBound).center; + } + return means; + } + + + @Override + public void quantize(int[] pixels, int maxColors) { + + } + + @Override + public List<Palette.Swatch> getQuantizedColors() { + return mSwatches; + } + + private void predict(int maxColors, int iterationsCompleted) { + double[][] centroidDistance = new double[maxColors][maxColors]; + for (int i = 0; i <= maxColors; i++) { + for (int j = i + 1; j < maxColors; j++) { + float[] meanI = mMeans[i].center; + float[] meanJ = mMeans[j].center; + double distance = mCentroidProvider.distance(meanI, meanJ); + centroidDistance[i][j] = distance; + centroidDistance[j][i] = distance; + } + } + + // Construct a K×K matrix M in which row i is a permutation of + // 1,2,…,K that represents the clusters in increasing order of + // distance of their centers from ci; + int[][] distanceMatrix = new int[maxColors][maxColors]; + for (int i = 0; i < maxColors; i++) { + double[] distancesFromIToAnotherMean = centroidDistance[i]; + double[] sortedByDistanceAscending = distancesFromIToAnotherMean.clone(); + Arrays.sort(sortedByDistanceAscending); + int[] outputRow = new int[maxColors]; + for (int j = 0; j < maxColors; j++) { + outputRow[j] = findIndex(distancesFromIToAnotherMean, sortedByDistanceAscending[j]); + } + distanceMatrix[i] = outputRow; + } + + // for (i=1;i≤N′;i=i+ 1) do + // Let Sp be the cluster that xi was assigned to in the previous + // iteration; + // p=m[i]; + // min_dist=prev_dist=jjxi−cpjj2; + boolean anyColorMoved = false; + for (int intColor : mUniqueColors) { + float[] color = mCentroidProvider.getCentroid(intColor); + int indexOfCurrentMean = mMeanIndexByColor.get(intColor); + Mean currentMean = mMeans[indexOfCurrentMean]; + double minDistance = mCentroidProvider.distance(color, currentMean.center); + for (int j = 1; j < maxColors; j++) { + int indexOfClusterFromCurrentToJ = distanceMatrix[indexOfCurrentMean][j]; + double distanceBetweenJAndCurrent = + centroidDistance[indexOfCurrentMean][indexOfClusterFromCurrentToJ]; + if (distanceBetweenJAndCurrent >= (4 * minDistance)) { + break; + } + double distanceBetweenJAndColor = mCentroidProvider.distance(mMeans[j].center, + color); + if (distanceBetweenJAndColor < minDistance) { + minDistance = distanceBetweenJAndColor; + mMeanIndexByColor.remove(intColor); + mMeanIndexByColor.put(intColor, j); + anyColorMoved = true; + } + } + } + + List<MeanBucket> buckets = new ArrayList<>(); + for (int i = 0; i < maxColors; i++) { + buckets.add(new MeanBucket()); + } + + for (int intColor : mUniqueColors) { + int meanIndex = mMeanIndexByColor.get(intColor); + MeanBucket meanBucket = buckets.get(meanIndex); + meanBucket.add(mCentroidProvider.getCentroid(intColor), intColor, + mCountByColor.get(intColor)); + } + + List<Palette.Swatch> swatches = new ArrayList<>(); + boolean done = !anyColorMoved && iterationsCompleted > 0 || iterationsCompleted >= 100; + if (done) { + for (int i = 0; i < buckets.size(); i++) { + MeanBucket a = buckets.get(i); + if (a.mCount <= 0) { + continue; + } + List<MeanBucket> bucketsToMerge = new ArrayList<>(); + for (int j = i + 1; j < buckets.size(); j++) { + MeanBucket b = buckets.get(j); + if (b.mCount == 0) { + continue; + } + float[] bCentroid = b.getCentroid(); + assert (a.mCount > 0); + assert (a.getCentroid() != null); + + assert (bCentroid != null); + if (mCentroidProvider.distance(a.getCentroid(), b.getCentroid()) < 5) { + bucketsToMerge.add(b); + } + } + + for (MeanBucket bucketToMerge : bucketsToMerge) { + float[] centroid = bucketToMerge.getCentroid(); + a.add(centroid, mCentroidProvider.getColor(centroid), bucketToMerge.mCount); + buckets.remove(bucketToMerge); + } + } + + for (MeanBucket bucket : buckets) { + float[] centroid = bucket.getCentroid(); + if (centroid == null) { + continue; + } + + int rgb = mCentroidProvider.getColor(centroid); + swatches.add(new Palette.Swatch(rgb, bucket.mCount)); + mSwatches.clear(); + mSwatches.addAll(swatches); + } + } else { + List<MeanBucket> emptyBuckets = new ArrayList<>(); + for (int i = 0; i < buckets.size(); i++) { + MeanBucket bucket = buckets.get(i); + if ((bucket.getCentroid() == null) || (bucket.mCount == 0)) { + emptyBuckets.add(bucket); + for (Integer color : mUniqueColors) { + int meanIndex = mMeanIndexByColor.get(color); + if (meanIndex > i) { + mMeanIndexByColor.put(color, meanIndex--); + } + } + } + } + + Mean[] newMeans = new Mean[buckets.size()]; + for (int i = 0; i < buckets.size(); i++) { + float[] centroid = buckets.get(i).getCentroid(); + newMeans[i] = new Mean(centroid); + } + + predict(buckets.size(), iterationsCompleted + 1); + } + + } + + private static int findIndex(double[] list, double element) { + for (int i = 0; i < list.length; i++) { + if (list[i] == element) { + return i; + } + } + throw new IllegalArgumentException("Element not in list"); + } +} diff --git a/core/java/com/android/internal/graphics/palette/WuQuantizer.java b/core/java/com/android/internal/graphics/palette/WuQuantizer.java new file mode 100644 index 000000000000..01e45f613986 --- /dev/null +++ b/core/java/com/android/internal/graphics/palette/WuQuantizer.java @@ -0,0 +1,442 @@ +/* + * 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.graphics.palette; + + +import java.util.ArrayList; +import java.util.List; + +// All reference Wu implementations are based on the original C code by Wu. +// Comments on methods are the same as in the original implementation, and the comment below +// is the original class header. + +/** + * Wu's Color Quantizer (v. 2) (see Graphics Gems vol. II, pp. 126-133) Author: Xiaolin Wu + * + * <p>Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by + * inclusion-exclusion tricks. For speed no nearest neighbor search is done. Slightly better + * performance can be expected by more sophisticated but more expensive versions. + */ +public class WuQuantizer implements Quantizer { + private static final int MAX_COLORS = 256; + private static final int RED = 2; + private static final int GREEN = 1; + private static final int BLUE = 0; + + private static final int QUANT_SIZE = 33; + private final List<Palette.Swatch> mSwatches = new ArrayList<>(); + + @Override + public List<Palette.Swatch> getQuantizedColors() { + return mSwatches; + } + + private static final class Box { + int mR0; /* min value, exclusive */ + int mR1; /* max value, inclusive */ + int mG0; + int mG1; + int mB0; + int mB1; + int mVol; + } + + private final int mSize; /* image size, in bytes. */ + private int mMaxColors; + private int[] mQadd; + private final int[] mPixels; + + private final double[][][] mM2 = new double[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; + private final long[][][] mWt = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; + private final long[][][] mMr = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; + private final long[][][] mMg = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; + private final long[][][] mMb = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE]; + + public WuQuantizer(int[] pixels, int maxColorCount) { + if (pixels == null) { + pixels = new int[]{}; + } + this.mPixels = pixels; + this.mSize = pixels.length; + } + + @Override + public void quantize(int[] colors, int maxColorCount) { + // All of the sample Wu implementations are reimplementations of a snippet of C code from + // the early 90s. They all cap the maximum # of colors at 256, and it is impossible to tell + // if this is a requirement, a consequence of QUANT_SIZE, or arbitrary. + this.mMaxColors = Math.min(MAX_COLORS, maxColorCount); + Box[] cube = new Box[mMaxColors]; + int red, green, blue; + + int next, i, k; + long weight; + double[] vv = new double[mMaxColors]; + double temp; + + compute3DHistogram(mWt, mMr, mMg, mMb, mM2); + computeMoments(mWt, mMr, mMg, mMb, mM2); + + for (i = 0; i < mMaxColors; i++) { + cube[i] = new Box(); + } + + cube[0].mR0 = cube[0].mG0 = cube[0].mB0 = 0; + cube[0].mR1 = cube[0].mG1 = cube[0].mB1 = QUANT_SIZE - 1; + next = 0; + + for (i = 1; i < mMaxColors; ++i) { + if (cut(cube[next], cube[i])) { + vv[next] = (cube[next].mVol > 1) ? getVariance(cube[next]) : 0.0f; + vv[i] = (cube[i].mVol > 1) ? getVariance(cube[i]) : 0.0f; + } else { + vv[next] = 0.0f; + i--; + } + next = 0; + temp = vv[0]; + for (k = 1; k <= i; ++k) { + if (vv[k] > temp) { + temp = vv[k]; + next = k; + } + } + if (temp <= 0.0f) { + break; + } + } + + for (k = 0; k < mMaxColors; ++k) { + weight = getVolume(cube[k], mWt); + if (weight > 0) { + red = (int) (getVolume(cube[k], mMr) / weight); + green = (int) (getVolume(cube[k], mMg) / weight); + blue = (int) (getVolume(cube[k], mMb) / weight); + colors[k] = ((red & 0x0ff) << 16) | ((green & 0x0ff) << 8) | (blue & 0x0ff); + } else { + colors[k] = 0; + } + } + + int bitsPerPixel = 0; + while ((1 << bitsPerPixel) < mMaxColors) { + bitsPerPixel++; + } + + List<Palette.Swatch> swatches = new ArrayList<>(); + for (int l = 0; l < k; l++) { + int pixel = colors[l]; + if (pixel == 0) { + continue; + } + swatches.add(new Palette.Swatch(pixel, 0)); + } + mSwatches.clear(); + mSwatches.addAll(swatches); + } + + /* Histogram is in elements 1..HISTSIZE along each axis, + * element 0 is for base or marginal value + * NB: these must start out 0! + */ + private void compute3DHistogram( + long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) { + // build 3-D color histogram of counts, r/g/b, and c^2 + int r, g, b; + int i; + int inr; + int ing; + int inb; + int[] table = new int[256]; + + for (i = 0; i < 256; i++) { + table[i] = i * i; + } + + mQadd = new int[mSize]; + + for (i = 0; i < mSize; ++i) { + int rgb = mPixels[i]; + // Skip less than opaque pixels. They're not meaningful in the context of palette + // generation for UI schemes. + if ((rgb >>> 24) < 0xff) { + continue; + } + r = ((rgb >> 16) & 0xff); + g = ((rgb >> 8) & 0xff); + b = (rgb & 0xff); + inr = (r >> 3) + 1; + ing = (g >> 3) + 1; + inb = (b >> 3) + 1; + mQadd[i] = (inr << 10) + (inr << 6) + inr + (ing << 5) + ing + inb; + /*[inr][ing][inb]*/ + ++vwt[inr][ing][inb]; + vmr[inr][ing][inb] += r; + vmg[inr][ing][inb] += g; + vmb[inr][ing][inb] += b; + m2[inr][ing][inb] += table[r] + table[g] + table[b]; + } + } + + /* At conclusion of the histogram step, we can interpret + * wt[r][g][b] = sum over voxel of P(c) + * mr[r][g][b] = sum over voxel of r*P(c) , similarly for mg, mb + * m2[r][g][b] = sum over voxel of c^2*P(c) + * Actually each of these should be divided by 'size' to give the usual + * interpretation of P() as ranging from 0 to 1, but we needn't do that here. + * + * We now convert histogram into moments so that we can rapidly calculate + * the sums of the above quantities over any desired box. + */ + private void computeMoments( + long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) { + /* compute cumulative moments. */ + int i, r, g, b; + int line, line_r, line_g, line_b; + int[] area = new int[QUANT_SIZE]; + int[] area_r = new int[QUANT_SIZE]; + int[] area_g = new int[QUANT_SIZE]; + int[] area_b = new int[QUANT_SIZE]; + double line2; + double[] area2 = new double[QUANT_SIZE]; + + for (r = 1; r < QUANT_SIZE; ++r) { + for (i = 0; i < QUANT_SIZE; ++i) { + area2[i] = area[i] = area_r[i] = area_g[i] = area_b[i] = 0; + } + for (g = 1; g < QUANT_SIZE; ++g) { + line2 = line = line_r = line_g = line_b = 0; + for (b = 1; b < QUANT_SIZE; ++b) { + line += vwt[r][g][b]; + line_r += vmr[r][g][b]; + line_g += vmg[r][g][b]; + line_b += vmb[r][g][b]; + line2 += m2[r][g][b]; + + area[b] += line; + area_r[b] += line_r; + area_g[b] += line_g; + area_b[b] += line_b; + area2[b] += line2; + + vwt[r][g][b] = vwt[r - 1][g][b] + area[b]; + vmr[r][g][b] = vmr[r - 1][g][b] + area_r[b]; + vmg[r][g][b] = vmg[r - 1][g][b] + area_g[b]; + vmb[r][g][b] = vmb[r - 1][g][b] + area_b[b]; + m2[r][g][b] = m2[r - 1][g][b] + area2[b]; + } + } + } + } + + private long getVolume(Box cube, long[][][] mmt) { + /* Compute sum over a box of any given statistic */ + return (mmt[cube.mR1][cube.mG1][cube.mB1] + - mmt[cube.mR1][cube.mG1][cube.mB0] + - mmt[cube.mR1][cube.mG0][cube.mB1] + + mmt[cube.mR1][cube.mG0][cube.mB0] + - mmt[cube.mR0][cube.mG1][cube.mB1] + + mmt[cube.mR0][cube.mG1][cube.mB0] + + mmt[cube.mR0][cube.mG0][cube.mB1] + - mmt[cube.mR0][cube.mG0][cube.mB0]); + } + + /* The next two routines allow a slightly more efficient calculation + * of Vol() for a proposed subbox of a given box. The sum of Top() + * and Bottom() is the Vol() of a subbox split in the given direction + * and with the specified new upper bound. + */ + private long getBottom(Box cube, int dir, long[][][] mmt) { + /* Compute part of Vol(cube, mmt) that doesn't depend on r1, g1, or b1 */ + /* (depending on dir) */ + switch (dir) { + case RED: + return (-mmt[cube.mR0][cube.mG1][cube.mB1] + + mmt[cube.mR0][cube.mG1][cube.mB0] + + mmt[cube.mR0][cube.mG0][cube.mB1] + - mmt[cube.mR0][cube.mG0][cube.mB0]); + case GREEN: + return (-mmt[cube.mR1][cube.mG0][cube.mB1] + + mmt[cube.mR1][cube.mG0][cube.mB0] + + mmt[cube.mR0][cube.mG0][cube.mB1] + - mmt[cube.mR0][cube.mG0][cube.mB0]); + case BLUE: + return (-mmt[cube.mR1][cube.mG1][cube.mB0] + + mmt[cube.mR1][cube.mG0][cube.mB0] + + mmt[cube.mR0][cube.mG1][cube.mB0] + - mmt[cube.mR0][cube.mG0][cube.mB0]); + default: + return 0; + } + } + + private long getTop(Box cube, int dir, int pos, long[][][] mmt) { + /* Compute remainder of Vol(cube, mmt), substituting pos for */ + /* r1, g1, or b1 (depending on dir) */ + switch (dir) { + case RED: + return (mmt[pos][cube.mG1][cube.mB1] + - mmt[pos][cube.mG1][cube.mB0] + - mmt[pos][cube.mG0][cube.mB1] + + mmt[pos][cube.mG0][cube.mB0]); + case GREEN: + return (mmt[cube.mR1][pos][cube.mB1] + - mmt[cube.mR1][pos][cube.mB0] + - mmt[cube.mR0][pos][cube.mB1] + + mmt[cube.mR0][pos][cube.mB0]); + case BLUE: + return (mmt[cube.mR1][cube.mG1][pos] + - mmt[cube.mR1][cube.mG0][pos] + - mmt[cube.mR0][cube.mG1][pos] + + mmt[cube.mR0][cube.mG0][pos]); + default: + return 0; + } + } + + private double getVariance(Box cube) { + /* Compute the weighted variance of a box */ + /* NB: as with the raw statistics, this is really the variance * size */ + double dr, dg, db, xx; + dr = getVolume(cube, mMr); + dg = getVolume(cube, mMg); + db = getVolume(cube, mMb); + xx = + mM2[cube.mR1][cube.mG1][cube.mB1] + - mM2[cube.mR1][cube.mG1][cube.mB0] + - mM2[cube.mR1][cube.mG0][cube.mB1] + + mM2[cube.mR1][cube.mG0][cube.mB0] + - mM2[cube.mR0][cube.mG1][cube.mB1] + + mM2[cube.mR0][cube.mG1][cube.mB0] + + mM2[cube.mR0][cube.mG0][cube.mB1] + - mM2[cube.mR0][cube.mG0][cube.mB0]; + return xx - (dr * dr + dg * dg + db * db) / getVolume(cube, mWt); + } + + /* We want to minimize the sum of the variances of two subboxes. + * The sum(c^2) terms can be ignored since their sum over both subboxes + * is the same (the sum for the whole box) no matter where we split. + * The remaining terms have a minus sign in the variance formula, + * so we drop the minus sign and MAXIMIZE the sum of the two terms. + */ + private double maximize( + Box cube, + int dir, + int first, + int last, + int[] cut, + long wholeR, + long wholeG, + long wholeB, + long wholeW) { + long half_r, half_g, half_b, half_w; + long base_r, base_g, base_b, base_w; + int i; + double temp, max; + + base_r = getBottom(cube, dir, mMr); + base_g = getBottom(cube, dir, mMg); + base_b = getBottom(cube, dir, mMb); + base_w = getBottom(cube, dir, mWt); + + max = 0.0f; + cut[0] = -1; + + for (i = first; i < last; ++i) { + half_r = base_r + getTop(cube, dir, i, mMr); + half_g = base_g + getTop(cube, dir, i, mMg); + half_b = base_b + getTop(cube, dir, i, mMb); + half_w = base_w + getTop(cube, dir, i, mWt); + /* now half_x is sum over lower half of box, if split at i */ + if (half_w == 0) /* subbox could be empty of pixels! */ { + continue; /* never split into an empty box */ + } + temp = (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w; + half_r = wholeR - half_r; + half_g = wholeG - half_g; + half_b = wholeB - half_b; + half_w = wholeW - half_w; + if (half_w == 0) /* subbox could be empty of pixels! */ { + continue; /* never split into an empty box */ + } + temp += (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w; + + if (temp > max) { + max = temp; + cut[0] = i; + } + } + + return max; + } + + private boolean cut(Box set1, Box set2) { + int dir; + int[] cutr = new int[1]; + int[] cutg = new int[1]; + int[] cutb = new int[1]; + double maxr, maxg, maxb; + long whole_r, whole_g, whole_b, whole_w; + + whole_r = getVolume(set1, mMr); + whole_g = getVolume(set1, mMg); + whole_b = getVolume(set1, mMb); + whole_w = getVolume(set1, mWt); + + maxr = maximize(set1, RED, set1.mR0 + 1, set1.mR1, cutr, whole_r, whole_g, whole_b, + whole_w); + maxg = maximize(set1, GREEN, set1.mG0 + 1, set1.mG1, cutg, whole_r, whole_g, whole_b, + whole_w); + maxb = maximize(set1, BLUE, set1.mB0 + 1, set1.mB1, cutb, whole_r, whole_g, whole_b, + whole_w); + + if (maxr >= maxg && maxr >= maxb) { + dir = RED; + if (cutr[0] < 0) return false; /* can't split the box */ + } else if (maxg >= maxr && maxg >= maxb) { + dir = GREEN; + } else { + dir = BLUE; + } + + set2.mR1 = set1.mR1; + set2.mG1 = set1.mG1; + set2.mB1 = set1.mB1; + + switch (dir) { + case RED: + set2.mR0 = set1.mR1 = cutr[0]; + set2.mG0 = set1.mG0; + set2.mB0 = set1.mB0; + break; + case GREEN: + set2.mG0 = set1.mG1 = cutg[0]; + set2.mR0 = set1.mR0; + set2.mB0 = set1.mB0; + break; + case BLUE: + set2.mB0 = set1.mB1 = cutb[0]; + set2.mR0 = set1.mR0; + set2.mG0 = set1.mG0; + break; + } + set1.mVol = (set1.mR1 - set1.mR0) * (set1.mG1 - set1.mG0) * (set1.mB1 - set1.mB0); + set2.mVol = (set2.mR1 - set2.mR0) * (set2.mG1 - set2.mG0) * (set2.mB1 - set2.mB0); + + return true; + } +} diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index e82cc737a395..342456a58091 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -25,6 +25,9 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADL import static android.view.SurfaceControl.JankData.PREDICTION_ERROR; import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING; +import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED; +import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.HardwareRendererObserver; @@ -72,6 +75,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private long mEndVsyncId = INVALID_ID; private boolean mMetricsFinalized; private boolean mCancelled = false; + private FrameTrackerListener mListener; private static class JankInfo { long frameVsyncId; @@ -109,7 +113,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, - int traceThresholdFrameTimeMillis) { + int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) { mSession = session; mRendererWrapper = renderer; mMetricsWrapper = metrics; @@ -120,6 +124,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; + mListener = listener; // If the surface isn't valid yet, wait until it's created. if (viewRootWrapper.getSurfaceControl().isValid()) { @@ -165,11 +170,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ public synchronized void begin() { mBeginVsyncId = mChoreographer.getVsyncId() + 1; + mSession.setTimeStamp(System.nanoTime()); Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); mRendererWrapper.addObserver(mObserver); if (mSurfaceControl != null) { mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); } + if (mListener != null) { + mListener.onNotifyCujEvents(mSession, ACTION_SESSION_BEGIN); + } } /** @@ -224,7 +233,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } private boolean isInRange(long vsyncId) { - // It's possible that we may miss a callback for the frame with vsyncId == mEndVsyncId. // Because of that, we collect all frames even if they happen after the end so we eventually // have a frame after the end with both callbacks present. @@ -371,6 +379,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener missedAppFramesCount + missedSfFramesCounts, maxFrameTimeNanos, missedSfFramesCounts); + if (mListener != null) { + mListener.onNotifyCujEvents(mSession, ACTION_METRICS_LOGGED); + } } if (DEBUG) { Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() @@ -495,4 +506,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener return mChoreographer.getVsyncId(); } } + + /** + * A listener that notifies cuj events. + */ + public interface FrameTrackerListener { + /** + * Notify that the CUJ session was created. + * + * @param session the CUJ session + * @param action the specific action + */ + void onNotifyCujEvents(Session session, String action); + } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index cba6af98a980..0294ec398484 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -16,6 +16,8 @@ package com.android.internal.jank; +import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; + import static com.android.internal.jank.FrameTracker.ChoreographerWrapper; import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; @@ -48,9 +50,12 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import android.annotation.IntDef; import android.annotation.NonNull; +import android.content.Context; +import android.content.Intent; import android.os.Build; import android.os.HandlerExecutor; import android.os.HandlerThread; +import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; @@ -59,6 +64,7 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; +import com.android.internal.jank.FrameTracker.FrameTrackerListener; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; @@ -74,6 +80,8 @@ import java.util.concurrent.TimeUnit; */ public class InteractionJankMonitor { private static final String TAG = InteractionJankMonitor.class.getSimpleName(); + private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); + private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); private static final String SETTINGS_ENABLED_KEY = "enabled"; @@ -90,6 +98,14 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3; private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; + public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; + public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; + public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; + public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; + public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; + @VisibleForTesting + public static final String PROP_NOTIFY_CUJ_EVENT = "debug.notify_cuj_events"; + // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1; @@ -256,15 +272,28 @@ public class InteractionJankMonitor { */ @VisibleForTesting public FrameTracker createFrameTracker(View v, Session session) { + final Context c = v.getContext().getApplicationContext(); synchronized (this) { + boolean needListener = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false); + FrameTrackerListener eventsListener = + !needListener ? null : (s, act) -> notifyEvents(c, act, s); + return new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(v.getThreadedRenderer()), new ViewRootWrapper(v.getViewRootImpl()), new SurfaceControlWrapper(), new ChoreographerWrapper(Choreographer.getInstance()), mMetrics, - mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis); + mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener); } } + private void notifyEvents(Context context, String action, Session session) { + Intent intent = new Intent(action); + intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj())); + intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp()); + intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcast(intent); + } + /** * Begin a trace session. * @@ -479,6 +508,7 @@ public class InteractionJankMonitor { public static class Session { @CujType private int mCujType; + private long mTimeStamp; public Session(@CujType int cujType) { mCujType = cujType; @@ -505,5 +535,13 @@ public class InteractionJankMonitor { public String getName() { return "J<" + getNameOfCuj(mCujType) + ">"; } + + public void setTimeStamp(long timeStamp) { + mTimeStamp = timeStamp; + } + + public long getTimeStamp() { + return mTimeStamp; + } } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 1f8ffe08a257..62e1ee19b25e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -7965,16 +7965,14 @@ public class BatteryStatsImpl extends BatteryStats { /** Adds the given energy to the given standard energy bucket for this uid. */ private void addEnergyToStandardBucketLocked(long energyDeltaUJ, - @StandardEnergyBucket int energyBucket, boolean accumulate) { + @StandardEnergyBucket int energyBucket) { getOrCreateMeasuredEnergyStatsLocked() - .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate); + .updateStandardBucket(energyBucket, energyDeltaUJ); } /** Adds the given energy to the given custom energy bucket for this uid. */ - private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket, - boolean accumulate) { - getOrCreateMeasuredEnergyStatsLocked() - .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate); + private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket) { + getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(energyBucket, energyDeltaUJ); } /** @@ -12468,7 +12466,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ, true); + mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ); // Now we blame individual apps, but only if the display was ON. if (energyBucket != MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON) { @@ -12506,7 +12504,7 @@ public class BatteryStatsImpl extends BatteryStats { final long appDisplayEnergyMJ = (totalDisplayEnergyMJ * fgTimeMs + (totalFgTimeMs / 2)) / totalFgTimeMs; - uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true); + uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket); // To mitigate round-off errors, remove this app from numerator & denominator totals totalDisplayEnergyMJ -= appDisplayEnergyMJ; @@ -12520,6 +12518,7 @@ public class BatteryStatsImpl extends BatteryStats { * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called. * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called. * Data inside uidEnergies will not be modified (treated immutable). + * Uids not already known to BatteryStats will be ignored. */ public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket, long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) { @@ -12532,7 +12531,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mGlobalMeasuredEnergyStats == null) return; if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return; - mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true); + mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ); if (uidEnergies == null) return; final int numUids = uidEnergies.size(); @@ -12540,10 +12539,20 @@ public class BatteryStatsImpl extends BatteryStats { final int uidInt = mapUid(uidEnergies.keyAt(i)); final long uidEnergyUJ = uidEnergies.valueAt(i); if (uidEnergyUJ == 0) continue; - // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this, - // or converse problem of not creating a new Uid if its first blame is recorded here. - final Uid uidObj = getUidStatsLocked(uidInt); - uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true); + final Uid uidObj = getAvailableUidStatsLocked(uidInt); + if (uidObj != null) { + uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket); + } else { + // Ignore any uid not already known to BatteryStats, rather than creating a new Uid. + // Otherwise we could end up reviving dead Uids. Note that the CPU data is updated + // first, so any uid that has used any CPU should already be known to BatteryStats. + // Recently removed uids (especially common for isolated uids) can reach this path + // and are ignored. + if (!Process.isIsolated(uidInt)) { + Slog.w(TAG, "Received measured energy " + totalEnergyUJ + " for custom bucket " + + customEnergyBucket + " for non-existent uid " + uidInt); + } + } } } diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index eef9fa74e83a..0163acc295fb 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -22,10 +22,12 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Bundle; -import android.os.SystemClock; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.List; @@ -85,7 +87,7 @@ public class BatteryUsageStatsProvider { } /** - * Returns a snapshot of battery attribution data. + * Returns snapshots of battery attribution data, one per supplied query. */ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { @@ -112,12 +114,19 @@ public class BatteryUsageStatsProvider { return results; } - private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { + /** + * Returns a snapshot of battery attribution data. + */ + @VisibleForTesting + public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { + final long realtimeUs = mStats.mClocks.elapsedRealtime() * 1000; + final long uptimeUs = mStats.mClocks.uptimeMillis() * 1000; + final long[] customMeasuredEnergiesMicroJoules = mStats.getCustomMeasuredEnergiesMicroJoules(); final int customPowerComponentCount = customMeasuredEnergiesMicroJoules != null - ? customMeasuredEnergiesMicroJoules.length - : 0; + ? customMeasuredEnergiesMicroJoules.length + : 0; // TODO(b/174186358): read extra time component number from configuration final int customTimeComponentCount = 0; @@ -128,12 +137,14 @@ public class BatteryUsageStatsProvider { SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats(); for (int i = uidStats.size() - 1; i >= 0; i--) { - batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); + final BatteryStats.Uid uid = uidStats.valueAt(i); + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, + getProcessBackgroundTimeMs(uid, realtimeUs)) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, + getProcessForegroundTimeMs(uid, realtimeUs)); } - final long realtimeUs = SystemClock.elapsedRealtime() * 1000; - final long uptimeUs = SystemClock.uptimeMillis() * 1000; - final List<PowerCalculator> powerCalculators = getPowerCalculators(); for (int i = 0, count = powerCalculators.size(); i < count; i++) { PowerCalculator powerCalculator = powerCalculators.get(i); @@ -143,4 +154,35 @@ public class BatteryUsageStatsProvider { return batteryUsageStatsBuilder.build(); } + + private long getProcessForegroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { + final long topStateDurationMs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP, + realtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; + + long foregroundActivityDurationMs = 0; + final BatteryStats.Timer foregroundActivityTimer = uid.getForegroundActivityTimer(); + if (foregroundActivityTimer != null) { + foregroundActivityDurationMs = foregroundActivityTimer.getTotalTimeLocked(realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } + + // Use the min value of STATE_TOP time and foreground activity time, since both of these + // times are imprecise + final long foregroundDurationMs = Math.min(topStateDurationMs, + foregroundActivityDurationMs); + + long foregroundServiceDurationMs = 0; + final BatteryStats.Timer foregroundServiceTimer = uid.getForegroundServiceTimer(); + if (foregroundServiceTimer != null) { + foregroundServiceDurationMs = foregroundServiceTimer.getTotalTimeLocked(realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } + + return foregroundDurationMs + foregroundServiceDurationMs; + } + + private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { + return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } } diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 9698f190a419..791e9ad5ef9d 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -1,6 +1,23 @@ { "presubmit": [ { + "file_patterns": ["Battery[^/]*\\.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["Battery[^/]*\\.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] + }, + { "name": "FrameworksCoreTests", "options": [ { diff --git a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java index 5910b61f0f6f..fd5201431eab 100644 --- a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java +++ b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java @@ -32,6 +32,10 @@ public class UsageBasedPowerEstimator { mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR; } + public boolean isSupported() { + return mAveragePowerMahPerMs != 0; + } + /** * Given a {@link BatteryStats.Timer}, returns the accumulated duration. */ diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 63763f76c6ed..98f613fc1c40 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -15,8 +15,13 @@ */ package com.android.internal.os; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; @@ -30,25 +35,93 @@ import java.util.List; public class WifiPowerCalculator extends PowerCalculator { private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private static final String TAG = "WifiPowerCalculator"; - private final double mIdleCurrentMa; - private final double mTxCurrentMa; - private final double mRxCurrentMa; - private final PowerProfile mPowerProfile; + private final UsageBasedPowerEstimator mIdlePowerEstimator; + private final UsageBasedPowerEstimator mTxPowerEstimator; + private final UsageBasedPowerEstimator mRxPowerEstimator; + private final UsageBasedPowerEstimator mPowerOnPowerEstimator; + private final UsageBasedPowerEstimator mScanPowerEstimator; + private final UsageBasedPowerEstimator mBatchScanPowerEstimator; private final boolean mHasWifiPowerController; - private double mTotalAppPowerDrain = 0; - private long mTotalAppRunningTime = 0; - private WifiPowerEstimator mWifiPowerEstimator; - private boolean mHasWifiPowerReporting; + private final double mWifiPowerPerPacket; + + private static class PowerDurationAndTraffic { + public double powerMah; + public long durationMs; + + public long wifiRxPackets; + public long wifiTxPackets; + public long wifiRxBytes; + public long wifiTxBytes; + } public WifiPowerCalculator(PowerProfile profile) { - mPowerProfile = profile; + mPowerOnPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_ON)); + mScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)); + mBatchScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN)); + mIdlePowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE)); + mTxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX)); + mRxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX)); + + mWifiPowerPerPacket = getWifiPowerPerPacket(profile); + + mHasWifiPowerController = + mIdlePowerEstimator.isSupported() && mTxPowerEstimator.isSupported() + && mRxPowerEstimator.isSupported(); + } + + @Override + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + + // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, + // so always check this field. + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); + + final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = + builder.getOrCreateSystemBatteryConsumerBuilder( + SystemBatteryConsumer.DRAIN_TYPE_WIFI); - mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); - mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX); - mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting); - mHasWifiPowerController = mIdleCurrentMa != 0 && mTxCurrentMa != 0 && mRxCurrentMa != 0; - mWifiPowerEstimator = new WifiPowerEstimator(mPowerProfile); + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs); + app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); + + if (app.getUid() == Process.WIFI_UID) { + systemBatteryConsumerBuilder.addUidBatteryConsumer(app); + app.excludeFromBatteryUsageStats(); + } + } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + systemBatteryConsumerBuilder + .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); } /** @@ -64,100 +137,151 @@ public class WifiPowerCalculator extends PowerCalculator { // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, // so always check this field. - mHasWifiPowerReporting = mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); + final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + calculateApp(powerDurationAndTraffic, app.uidObj, rawRealtimeUs, statsType, + hasWifiPowerReporting); + + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.wifiPowerMah = powerDurationAndTraffic.powerMah; + app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs; + app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes; + app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets; + app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes; + app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets; + if (app.getUid() == Process.WIFI_UID) { + if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); + app.isAggregated = true; + bs.add(app); + } } } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, statsType, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs; + bs.wifiPowerMah += powerDurationAndTraffic.powerMah; + if (bs.sumPower() > 0) { sippers.add(bs); } } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateApp(app, u, rawRealtimeUs, rawUptimeUs, statsType); - return; - } - - final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); - if (counter == null) { - return; - } - - final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = idleTime + rxTime + txTime; - mTotalAppRunningTime += app.wifiRunningTimeMs; - - app.wifiPowerMah = - ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) - / (1000 * 60 * 60); - mTotalAppPowerDrain += app.wifiPowerMah; - - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, + private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u, + long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting) { + powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, + powerDurationAndTraffic.wifiRxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" + - txTime + "ms power=" + formatCharge(app.wifiPowerMah)); - } - } + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); + if (counter != null) { + final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); + + powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; + powerDurationAndTraffic.powerMah = mIdlePowerEstimator.calculatePower(idleTime) + + mTxPowerEstimator.calculatePower(txTime) + + mRxPowerEstimator.calculatePower(rxTime); - private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateRemaining(app, stats, rawRealtimeUs, rawUptimeUs, - statsType); - return; + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + + "ms tx=" + txTime + "ms power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } + } + } else { + final double wifiPacketPower = ( + powerDurationAndTraffic.wifiRxPackets + powerDurationAndTraffic.wifiTxPackets) + * mWifiPowerPerPacket; + final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; + long batchScanTimeMs = 0; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + batchScanTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; + } + + powerDurationAndTraffic.durationMs = wifiRunningTime; + powerDurationAndTraffic.powerMah = wifiPacketPower + + mPowerOnPowerEstimator.calculatePower(wifiRunningTime) + + mScanPowerEstimator.calculatePower(wifiScanTimeMs) + + mBatchScanPowerEstimator.calculatePower(batchScanTimeMs); + + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } } + } - final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity(); + private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic, + BatteryStats stats, long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting, long totalAppDurationMs, + double totalAppPowerMah) { + long totalDurationMs; + double totalPowerMah; + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = + stats.getWifiControllerActivity(); - final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); + final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = Math.max(0, - (idleTimeMs + rxTimeMs + txTimeMs) - mTotalAppRunningTime); + totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs; - double powerDrainMah = counter.getPowerCounter().getCountLocked(statsType) - / (double) (1000 * 60 * 60); - if (powerDrainMah == 0) { - // Some controllers do not report power drain, so we can calculate it here. - powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) - + (rxTimeMs * mRxCurrentMa)) / (1000 * 60 * 60); + totalPowerMah = + counter.getPowerCounter().getCountLocked(statsType) / (double) (1000 * 60 * 60); + if (totalPowerMah == 0) { + // Some controllers do not report power drain, so we can calculate it here. + totalPowerMah = mIdlePowerEstimator.calculatePower(idleTimeMs) + + mTxPowerEstimator.calculatePower(txTimeMs) + + mRxPowerEstimator.calculatePower(rxTimeMs); + } + } else { + totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000; + totalPowerMah = mPowerOnPowerEstimator.calculatePower(totalDurationMs); } - app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain); + + powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs); + powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah); if (DEBUG) { - Log.d(TAG, "left over WiFi power: " + formatCharge(app.wifiPowerMah)); + Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah)); } } - @Override - public void reset() { - mTotalAppPowerDrain = 0; - mTotalAppRunningTime = 0; - mWifiPowerEstimator.reset(); + /** + * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. + */ + private static double getWifiPowerPerPacket(PowerProfile profile) { + // TODO(b/179392913): Extract average bit rates from system + final long wifiBps = 1000000; + final double averageWifiActivePower = + profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + return averageWifiActivePower / (((double) wifiBps) / 8 / 2048); } } diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java deleted file mode 100644 index d0a105cfd958..000000000000 --- a/core/java/com/android/internal/os/WifiPowerEstimator.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.os; - -import android.os.BatteryStats; -import android.os.Process; -import android.os.UserHandle; -import android.util.Log; -import android.util.SparseArray; - -import java.util.List; - -/** - * Estimates WiFi power usage based on timers in BatteryStats. - */ -public class WifiPowerEstimator extends PowerCalculator { - private static final boolean DEBUG = BatteryStatsHelper.DEBUG; - private static final String TAG = "WifiPowerEstimator"; - private final double mWifiPowerPerPacket; - private final double mWifiPowerOn; - private final double mWifiPowerScan; - private final double mWifiPowerBatchScan; - private long mTotalAppWifiRunningTimeMs = 0; - - public WifiPowerEstimator(PowerProfile profile) { - mWifiPowerPerPacket = getWifiPowerPerPacket(profile); - mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON); - mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN); - mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN); - } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); - - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); - - for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); - } - } - if (bs.sumPower() > 0) { - sippers.add(bs); - } - } - - /** - * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. - */ - private static double getWifiPowerPerPacket(PowerProfile profile) { - final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system - final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) - / 3600; - return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048); - } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - - final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) - * mWifiPowerPerPacket; - - app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; - mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; - final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); - - final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; - final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); - - double wifiBatchScanPower = 0; - for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { - final long batchScanTimeMs = - u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; - final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60); - wifiBatchScanPower += batchScanPower; - } - - app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(app.wifiPowerMah)); - } - } - - void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) - / 1000; - final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) - / (1000*60*60); - app.wifiRunningTimeMs = totalRunningTimeMs; - app.wifiPowerMah = Math.max(0, powerDrain); - } - - @Override - public void reset() { - mTotalAppWifiRunningTimeMs = 0; - } -} diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index c8afea9b0982..6b1d408bee9a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -18,8 +18,8 @@ package com.android.internal.os; import static android.system.OsConstants.O_CLOEXEC; -import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; - +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalServerSocket; @@ -36,17 +36,16 @@ import android.util.Log; import com.android.internal.net.NetworkUtilsInternal; +import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStreamReader; /** @hide */ public final class Zygote { @@ -103,7 +102,7 @@ public final class Zygote { */ public static final int PROFILE_FROM_SHELL = 1 << 15; - /** + /* * Enable using the ART app image startup cache */ public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16; @@ -116,6 +115,13 @@ public final class Zygote { */ public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17; + /** + * Disable runtime access to {@link android.annotation.TestApi} annotated members. + * + * <p>This only takes effect if Hidden API access restrictions are enabled as well. + */ + public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; + public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); public static final int MEMORY_TAG_LEVEL_NONE = 0; @@ -231,6 +237,8 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end="; + private static final String TAG = "Zygote"; + /** Prefix prepended to socket names created by init */ private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; @@ -401,6 +409,10 @@ public final class Zygote { // Note that this event ends at the end of handleChildProc. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + if (gids != null && gids.length > 0) { + NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); + } + // Set the Java Language thread priority to the default value for new apps. Thread.currentThread().setPriority(Thread.NORM_PRIORITY); @@ -573,114 +585,163 @@ public final class Zygote { private static native int nativeGetUsapPoolEventFD(); /** - * Fork a new unspecialized app process from the zygote + * Fork a new unspecialized app process from the zygote. Adds the Usap to the native + * Usap table. * * @param usapPoolSocket The server socket the USAP will call accept on - * @param sessionSocketRawFDs Anonymous session sockets that are currently open - * @param isPriorityFork Value controlling the process priority level until accept is called - * @return In the Zygote process this function will always return null; in unspecialized app - * processes this function will return a Runnable object representing the new - * application that is passed up from usapMain. + * @param sessionSocketRawFDs Anonymous session sockets that are currently open. + * These are closed in the child. + * @param isPriorityFork Raise the initial process priority level because this is on the + * critical path for application startup. + * @return In the child process, this returns a Runnable that waits for specialization + * info to start an app process. In the sygote/parent process this returns null. */ - static Runnable forkUsap(LocalServerSocket usapPoolSocket, - int[] sessionSocketRawFDs, - boolean isPriorityFork) { - FileDescriptor[] pipeFDs; + static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket, + int[] sessionSocketRawFDs, + boolean isPriorityFork) { + FileDescriptor readFD; + FileDescriptor writeFD; try { - pipeFDs = Os.pipe2(O_CLOEXEC); + FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC); + readFD = pipeFDs[0]; + writeFD = pipeFDs[1]; } catch (ErrnoException errnoEx) { throw new IllegalStateException("Unable to create USAP pipe.", errnoEx); } - int pid = - nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), - sessionSocketRawFDs, isPriorityFork); - + int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(), + sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork); if (pid == 0) { - IoUtils.closeQuietly(pipeFDs[0]); - return usapMain(usapPoolSocket, pipeFDs[1]); + IoUtils.closeQuietly(readFD); + return childMain(null, usapPoolSocket, writeFD); + } else if (pid == -1) { + // Fork failed. + return null; } else { - // The read-end of the pipe will be closed by the native code. - // See removeUsapTableEntry(); - IoUtils.closeQuietly(pipeFDs[1]); + // readFD will be closed by the native code. See removeUsapTableEntry(); + IoUtils.closeQuietly(writeFD); + nativeAddUsapTableEntry(pid, readFD.getInt$()); return null; } } - private static native int nativeForkUsap(int readPipeFD, - int writePipeFD, - int[] sessionSocketRawFDs, - boolean isPriorityFork); + private static native int nativeForkApp(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs, + boolean argsKnown, + boolean isPriorityFork); + + /** + * Add an entry for a new Usap to the table maintained in native code. + */ + @CriticalNative + private static native void nativeAddUsapTableEntry(int pid, int readPipeFD); + + /** + * Fork a new app process from the zygote. argBuffer contains a fork command that + * request neither a child zygote, nor a wrapped process. Continue to accept connections + * on the specified socket, use those to refill argBuffer, and continue to process + * sufficiently simple fork requests. We presume that the only open file descriptors + * requiring special treatment are the session socket embedded in argBuffer, and + * zygoteSocket. + * @param argBuffer containing initial command and the connected socket from which to + * read more + * @param zygoteSocket socket from which to obtain new connections when current argBuffer + * one is disconnected + * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different + * peer will cause us to return rather than perform the requested fork. + * @param minUid Minimum Uid enforced for all but first fork request. The caller checks + * the Uid policy for the initial request. + * @param firstNiceName name of first created process. Used for error reporting only. + * @return A Runnable in each child process, null in the parent. + * If this returns in then argBuffer still contains a command needing to be executed. + */ + static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer, + @NonNull FileDescriptor zygoteSocket, + int expectedUid, + int minUid, + @Nullable String firstNiceName) { + boolean in_child = + argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName); + if (in_child) { + return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null); + } else { + return null; + } + } /** - * This function is used by unspecialized app processes to wait for specialization requests from - * the system server. + * Specialize the current process into one described by argBuffer or the command read from + * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close + * it. Used both for a specializing a USAP process, and for process creation without USAPs. + * In both cases, we specialize the process after first returning to Java code. * * @param writePipe The write end of the reporting pipe used to communicate with the poll loop * of the ZygoteServer. * @return A runnable oject representing the new application. */ - private static Runnable usapMain(LocalServerSocket usapPoolSocket, - FileDescriptor writePipe) { + private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer, + @Nullable LocalServerSocket usapPoolSocket, + FileDescriptor writePipe) { final int pid = Process.myPid(); - Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); - LocalSocket sessionSocket = null; DataOutputStream usapOutputStream = null; - Credentials peerCredentials = null; ZygoteArguments args = null; - // Change the priority to max before calling accept so we can respond to new specialization - // requests as quickly as possible. This will be reverted to the default priority in the - // native specialization code. - boostUsapPriority(); - - while (true) { - try { - sessionSocket = usapPoolSocket.accept(); - - // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. - blockSigTerm(); - - BufferedReader usapReader = - new BufferedReader(new InputStreamReader(sessionSocket.getInputStream())); - usapOutputStream = - new DataOutputStream(sessionSocket.getOutputStream()); + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + blockSigTerm(); - peerCredentials = sessionSocket.getPeerCredentials(); + LocalSocket sessionSocket = null; + if (argBuffer == null) { + // Read arguments from usapPoolSocket instead. - String[] argStrings = readArgumentList(usapReader); + Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); - if (argStrings != null) { - args = new ZygoteArguments(argStrings); + // Change the priority to max before calling accept so we can respond to new + // specialization requests as quickly as possible. This will be reverted to the + // default priority in the native specialization code. + boostUsapPriority(); + while (true) { + ZygoteCommandBuffer tmpArgBuffer = null; + try { + sessionSocket = usapPoolSocket.accept(); + + usapOutputStream = + new DataOutputStream(sessionSocket.getOutputStream()); + Credentials peerCredentials = sessionSocket.getPeerCredentials(); + tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket); + args = ZygoteArguments.getInstance(argBuffer); + applyUidSecurityPolicy(args, peerCredentials); // TODO (chriswailes): Should this only be run for debug builds? validateUsapCommand(args); break; - } else { - Log.e("USAP", "Truncated command received."); - IoUtils.closeQuietly(sessionSocket); - - // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. - unblockSigTerm(); + } catch (Exception ex) { + Log.e("USAP", ex.getMessage()); } - } catch (Exception ex) { - Log.e("USAP", ex.getMessage()); - IoUtils.closeQuietly(sessionSocket); - // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. unblockSigTerm(); + IoUtils.closeQuietly(sessionSocket); + IoUtils.closeQuietly(tmpArgBuffer); + blockSigTerm(); + } + } else { + try { + args = ZygoteArguments.getInstance(argBuffer); + } catch (Exception ex) { + Log.e("AppStartup", ex.getMessage()); + throw new AssertionError("Failed to parse application start command", ex); } + // peerCredentials were checked in parent. + } + if (args == null) { + throw new AssertionError("Empty command line"); } - try { - // SIGTERM is blocked on loop exit. This prevents a USAP that is specializing from - // being killed during a pool flush. - - setAppProcessName(args, "USAP"); + // SIGTERM is blocked here. This prevents a USAP that is specializing from being + // killed during a pool flush. - applyUidSecurityPolicy(args, peerCredentials); applyDebuggerSystemProperty(args); int[][] rlimits = null; @@ -689,53 +750,57 @@ public final class Zygote { rlimits = args.mRLimits.toArray(INT_ARRAY_2D); } - // This must happen before the SELinux policy for this process is - // changed when specializing. - try { - // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a - // Process.ProcessStartResult object. - usapOutputStream.writeInt(pid); - } catch (IOException ioEx) { - Log.e("USAP", "Failed to write response to session socket: " - + ioEx.getMessage()); - throw new RuntimeException(ioEx); - } finally { - IoUtils.closeQuietly(sessionSocket); - + if (argBuffer == null) { + // This must happen before the SELinux policy for this process is + // changed when specializing. try { - // This socket is closed using Os.close due to an issue with the implementation - // of LocalSocketImp.close(). Because the raw FD is created by init and then - // loaded from an environment variable (as opposed to being created by the - // LocalSocketImpl itself) the current implementation will not actually close - // the underlying FD. - // - // See b/130309968 for discussion of this issue. - Os.close(usapPoolSocket.getFileDescriptor()); - } catch (ErrnoException ex) { - Log.e("USAP", "Failed to close USAP pool socket"); - throw new RuntimeException(ex); + // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a + // Process.ProcessStartResult object. + usapOutputStream.writeInt(pid); + } catch (IOException ioEx) { + Log.e("USAP", "Failed to write response to session socket: " + + ioEx.getMessage()); + throw new RuntimeException(ioEx); + } finally { + try { + // Since the raw FD is created by init and then loaded from an environment + // variable (as opposed to being created by the LocalSocketImpl itself), + // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See + // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd). + // Thus closing the LocalSocket does not suffice. See b/130309968 for more + // discussion. + FileDescriptor fd = usapPoolSocket.getFileDescriptor(); + usapPoolSocket.close(); + Os.close(fd); + } catch (ErrnoException | IOException ex) { + Log.e("USAP", "Failed to close USAP pool socket"); + throw new RuntimeException(ex); + } } } - try { - ByteArrayOutputStream buffer = - new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); - DataOutputStream outputStream = new DataOutputStream(buffer); - - // This is written as a long so that the USAP reporting pipe and USAP pool event FD - // handlers in ZygoteServer.runSelectLoop can be unified. These two cases should - // both send/receive 8 bytes. - outputStream.writeLong(pid); - outputStream.flush(); - - Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); - } catch (Exception ex) { - Log.e("USAP", - String.format("Failed to write PID (%d) to pipe (%d): %s", - pid, writePipe.getInt$(), ex.getMessage())); - throw new RuntimeException(ex); - } finally { - IoUtils.closeQuietly(writePipe); + if (writePipe != null) { + try { + ByteArrayOutputStream buffer = + new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); + DataOutputStream outputStream = new DataOutputStream(buffer); + + // This is written as a long so that the USAP reporting pipe and USAP pool + // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two + // cases should both send/receive 8 bytes. + // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects + // a different format. + outputStream.writeLong(pid); + outputStream.flush(); + Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); + } catch (Exception ex) { + Log.e("USAP", + String.format("Failed to write PID (%d) to pipe (%d): %s", + pid, writePipe.getInt$(), ex.getMessage())); + throw new RuntimeException(ex); + } finally { + IoUtils.closeQuietly(writePipe); + } } specializeAppProcess(args.mUid, args.mGid, args.mGids, @@ -842,13 +907,29 @@ public final class Zygote { return nativeRemoveUsapTableEntry(usapPID); } + @CriticalNative private static native boolean nativeRemoveUsapTableEntry(int usapPID); /** - * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal + * Return the minimum child uid that the given peer is allowed to create. + * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal * operation. It may also specify any gid and setgroups() list it chooses. * In factory test mode, it may specify any UID. - * + */ + static int minChildUid(Credentials peer) { + if (peer.getUid() == Process.SYSTEM_UID + && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + return Process.SYSTEM_UID; + } else { + return 0; + } + } + + /* + * Adjust uid and gid arguments, ensuring that the security policy is satisfied. * @param args non-null; zygote spawner arguments * @param peer non-null; peer credentials * @throws ZygoteSecurityException Indicates a security issue when applying the UID based @@ -857,17 +938,10 @@ public final class Zygote { static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) throws ZygoteSecurityException { - if (peer.getUid() == Process.SYSTEM_UID) { - /* In normal operation, SYSTEM_UID can only specify a restricted - * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. - */ - boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; - - if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) { - throw new ZygoteSecurityException( - "System UID may not launch process with UID < " - + Process.SYSTEM_UID); - } + if (args.mUidSpecified && (args.mUid < minChildUid(peer))) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); } // If not otherwise specified, uid and gid are inherited from peer @@ -953,45 +1027,6 @@ public final class Zygote { } /** - * Reads an argument list from the provided socket - * @return Argument list or null if EOF is reached - * @throws IOException passed straight through - */ - static String[] readArgumentList(BufferedReader socketReader) throws IOException { - int argc; - - try { - String argc_string = socketReader.readLine(); - - if (argc_string == null) { - // EOF reached. - return null; - } - argc = Integer.parseInt(argc_string); - - } catch (NumberFormatException ex) { - Log.e("Zygote", "Invalid Zygote wire format: non-int at argc"); - throw new IOException("Invalid wire format"); - } - - // See bug 1092107: large argc can be used for a DOS attack - if (argc > MAX_ZYGOTE_ARGC) { - throw new IOException("Max arg count exceeded"); - } - - String[] args = new String[argc]; - for (int arg_index = 0; arg_index < argc; arg_index++) { - args[arg_index] = socketReader.readLine(); - if (args[arg_index] == null) { - // We got an unexpected EOF. - throw new IOException("Truncated request"); - } - } - - return args; - } - - /** * Creates a managed LocalServerSocket object using a file descriptor * created by an init.rc script. The init scripts that specify the * sockets name can be found in system/core/rootdir. The socket is bound diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 32b808ab2528..65b454d47db2 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -16,8 +16,8 @@ package com.android.internal.os; +import java.io.EOFException; import java.util.ArrayList; -import java.util.Arrays; /** * Handles argument parsing for args related to the zygote spawner. @@ -245,20 +245,34 @@ class ZygoteArguments { /** * Constructs instance and parses args * - * @param args zygote command-line args + * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count. */ - ZygoteArguments(String[] args) throws IllegalArgumentException { - parseArgs(args); + private ZygoteArguments(ZygoteCommandBuffer args, int argCount) + throws IllegalArgumentException, EOFException { + parseArgs(args, argCount); + } + + /** + * Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return + * null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially + * positioned at the beginning of the command. + */ + public static ZygoteArguments getInstance(ZygoteCommandBuffer args) + throws IllegalArgumentException, EOFException { + int argCount = args.getCount(); + return argCount == 0 ? null : new ZygoteArguments(args, argCount); } /** * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and - * "--setgid=") and creates an array containing the remaining args. + * "--setgid=") and creates an array containing the remaining args. Return false if we were + * at EOF. * * Per security review bug #1112214, duplicate args are disallowed in critical cases to make * injection harder. */ - private void parseArgs(String[] args) throws IllegalArgumentException { + private void parseArgs(ZygoteCommandBuffer args, int argCount) + throws IllegalArgumentException, EOFException { /* * See android.os.ZygoteProcess.zygoteSendArgsAndGetResult() * Presently the wire format to the zygote process is: @@ -269,13 +283,13 @@ class ZygoteArguments { * the child or -1 on failure. */ - int curArg = 0; - + String unprocessedArg = null; + int curArg = 0; // Index of arg boolean seenRuntimeArgs = false; - boolean expectRuntimeArgs = true; - for ( /* curArg */ ; curArg < args.length; curArg++) { - String arg = args[curArg]; + + for ( /* curArg */ ; curArg < argCount; ++curArg) { + String arg = args.nextArg(); if (arg.equals("--")) { curArg++; @@ -367,7 +381,8 @@ class ZygoteArguments { "Duplicate arg specified"); } try { - mInvokeWith = args[++curArg]; + ++curArg; + mInvokeWith = args.nextArg(); } catch (IndexOutOfBoundsException ex) { throw new IllegalArgumentException( "--invoke-with requires argument"); @@ -397,12 +412,14 @@ class ZygoteArguments { } else if (arg.startsWith("--app-data-dir=")) { mAppDataDir = getAssignmentValue(arg); } else if (arg.equals("--preload-app")) { - mPreloadApp = args[++curArg]; + ++curArg; + mPreloadApp = args.nextArg(); } else if (arg.equals("--preload-package")) { - mPreloadPackage = args[++curArg]; - mPreloadPackageLibs = args[++curArg]; - mPreloadPackageLibFileName = args[++curArg]; - mPreloadPackageCacheKey = args[++curArg]; + curArg += 4; + mPreloadPackage = args.nextArg(); + mPreloadPackageLibs = args.nextArg(); + mPreloadPackageLibFileName = args.nextArg(); + mPreloadPackageCacheKey = args.nextArg(); } else if (arg.equals("--preload-default")) { mPreloadDefault = true; expectRuntimeArgs = false; @@ -411,8 +428,11 @@ class ZygoteArguments { } else if (arg.equals("--set-api-denylist-exemptions")) { // consume all remaining args; this is a stand-alone command, never included // with the regular fork command. - mApiDenylistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); - curArg = args.length; + mApiDenylistExemptions = new String[argCount - curArg - 1]; + ++curArg; + for (int i = 0; curArg < argCount; ++curArg, ++i) { + mApiDenylistExemptions[i] = args.nextArg(); + } expectRuntimeArgs = false; } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { String rateStr = getAssignmentValue(arg); @@ -462,35 +482,46 @@ class ZygoteArguments { } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) { mBindMountAppDataDirs = true; } else { + unprocessedArg = arg; break; } } + // curArg is the index of the first unprocessed argument. That argument is either referenced + // by unprocessedArg or not read yet. if (mBootCompleted) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException("Unexpected arguments after --boot-completed"); } } else if (mAbiListQuery || mPidQuery) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); } } else if (mPreloadPackage != null) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException( "Unexpected arguments after --preload-package."); } } else if (mPreloadApp != null) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException( "Unexpected arguments after --preload-app."); } } else if (expectRuntimeArgs) { if (!seenRuntimeArgs) { - throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); + throw new IllegalArgumentException("Unexpected argument : " + + (unprocessedArg == null ? args.nextArg() : unprocessedArg)); } - mRemainingArgs = new String[args.length - curArg]; - System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); + mRemainingArgs = new String[argCount - curArg]; + int i = 0; + if (unprocessedArg != null) { + mRemainingArgs[0] = unprocessedArg; + ++i; + } + for (; i < argCount - curArg; ++i) { + mRemainingArgs[i] = args.nextArg(); + } } if (mStartChildZygote) { diff --git a/core/java/com/android/internal/os/ZygoteCommandBuffer.java b/core/java/com/android/internal/os/ZygoteCommandBuffer.java new file mode 100644 index 000000000000..b61ae7acfacd --- /dev/null +++ b/core/java/com/android/internal/os/ZygoteCommandBuffer.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LocalSocket; + +import java.io.FileDescriptor; +import java.lang.ref.Reference; // For reachabilityFence. + +/** + * A native-accessible buffer for Zygote commands. Designed to support repeated forking + * of applications without intervening memory allocation, thus keeping zygote memory + * as stable as possible. + * A ZygoteCommandBuffer may have an associated socket from which it can be refilled. + * Otherwise the contents are explicitly set by getInstance(). + * + * NOT THREAD-SAFE. No methods may be called concurrently from multiple threads. + * + * Only one ZygoteCommandBuffer can exist at a time. + * Must be explicitly closed before being dropped. + * @hide + */ +class ZygoteCommandBuffer implements AutoCloseable { + private long mNativeBuffer; // Not final so that we can clear it in close(). + + /** + * The command socket. + * + * mSocket is retained in the child process in "peer wait" mode, so + * that it closes when the child process terminates. In other cases, + * it is closed in the peer. + */ + private final LocalSocket mSocket; + private final int mNativeSocket; + + /** + * Constructs instance from file descriptor from which the command will be read. + * Only a single instance may be live in a given process. The native code checks. + * + * @param fd file descriptor to read from. The setCommand() method may be used if and only if + * fd is null. + */ + ZygoteCommandBuffer(@Nullable LocalSocket socket) { + mSocket = socket; + if (socket == null) { + mNativeSocket = -1; + } else { + mNativeSocket = mSocket.getFileDescriptor().getInt$(); + } + mNativeBuffer = getNativeBuffer(mNativeSocket); + } + + /** + * Constructs an instance with explicitly supplied arguments and an invalid + * file descriptor. Can only be used for a single command. + */ + ZygoteCommandBuffer(@NonNull String[] args) { + this((LocalSocket) null); + setCommand(args); + } + + + private static native long getNativeBuffer(int fd); + + /** + * Deallocate native resources associated with the one and only command buffer, and prevent + * reuse. Subsequent calls to getInstance() will yield a new buffer. + * We do not close the associated socket, if any. + */ + @Override + public void close() { + freeNativeBuffer(mNativeBuffer); + mNativeBuffer = 0; + } + + private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer); + + /** + * Read at least the first line of the next command into the buffer, return the argument count + * from that line. Assumes we are initially positioned at the beginning of the first line of + * the command. Leave the buffer positioned at the beginning of the second command line, i.e. + * the first argument. If the buffer has no associated file descriptor, we just reposition to + * the beginning of the buffer, and reread existing contents. Returns zero if we started out + * at EOF. + */ + int getCount() { + try { + return nativeGetCount(mNativeBuffer); + } finally { + // Make sure the mNativeSocket doesn't get closed due to early finalization. + Reference.reachabilityFence(mSocket); + } + } + + private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer); + + + /* + * Set the buffer to contain the supplied sequence of arguments. + */ + private void setCommand(String[] command) { + int nArgs = command.length; + insert(mNativeBuffer, Integer.toString(nArgs)); + for (String s: command) { + insert(mNativeBuffer, s); + } + // Native code checks there is no socket; hence no reachabilityFence. + } + + private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s); + + /** + * Retrieve the next argument/line from the buffer, filling the buffer as necessary. + */ + String nextArg() { + try { + return nativeNextArg(mNativeBuffer); + } finally { + Reference.reachabilityFence(mSocket); + } + } + + private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer); + + void readFullyAndReset() { + try { + nativeReadFullyAndReset(mNativeBuffer); + } finally { + Reference.reachabilityFence(mSocket); + } + } + + private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer); + + /** + * Fork a child as specified by the current command in the buffer, and repeat this process + * after refilling the buffer, so long as the buffer clearly contains another fork command. + * + * @param zygoteSocket socket from which to obtain new connections when current one is + * disconnected + * @param expectedUid Peer UID for current connection. We refuse to deal with requests from + * a different UID. + * @param minUid the smallest uid that may be request for the child process. + * @param firstNiceName The name for the initial process to be forked. Used only for error + * reporting. + * + * @return true in the child, false in the parent. In the parent case, the buffer is positioned + * at the beginning of a command that still needs to be processed. + */ + boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid, + String firstNiceName) { + try { + return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(), + expectedUid, minUid, firstNiceName); + } finally { + Reference.reachabilityFence(mSocket); + Reference.reachabilityFence(zygoteSocket); + } + } + + /* + * Repeatedly fork children as above. It commonly does not return in the parent, but it may. + * @return true in the chaild, false in the parent if we encounter a command we couldn't handle. + */ + private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer, + int zygoteSocketRawFd, + int expectedUid, + int minUid, + String firstNiceName); + +} diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 5a576ebbc442..37c75907061c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -36,16 +36,15 @@ import android.system.StructPollfd; import android.util.Log; import dalvik.system.VMRuntime; +import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.concurrent.TimeUnit; @@ -67,7 +66,6 @@ class ZygoteConnection { private final LocalSocket mSocket; @UnsupportedAppUsage private final DataOutputStream mSocketOutStream; - private final BufferedReader mSocketReader; @UnsupportedAppUsage private final Credentials peer; private final String abiList; @@ -85,9 +83,6 @@ class ZygoteConnection { this.abiList = abiList; mSocketOutStream = new DataOutputStream(socket.getOutputStream()); - mSocketReader = - new BufferedReader( - new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE); mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS); @@ -111,178 +106,216 @@ class ZygoteConnection { } /** - * Reads one start command from the command socket. If successful, a child is forked and a + * Reads a command from the command socket. If a child is successfully forked, a * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child * process. {@code null} is always returned in the parent process (the zygote). + * If multipleOK is set, we may keep processing additional fork commands before returning. * * If the client closes the socket, an {@code EOF} condition is set, which callers can test * for by calling {@code ZygoteConnection.isClosedByPeer}. */ - Runnable processOneCommand(ZygoteServer zygoteServer) { - String[] args; - - try { - args = Zygote.readArgumentList(mSocketReader); - } catch (IOException ex) { - throw new IllegalStateException("IOException on command socket", ex); - } - - // readArgumentList returns null only when it has reached EOF with no available - // data to read. This will only happen when the remote socket has disconnected. - if (args == null) { - isEof = true; - return null; - } - - int pid; - FileDescriptor childPipeFd = null; - FileDescriptor serverPipeFd = null; - - ZygoteArguments parsedArgs = new ZygoteArguments(args); - - if (parsedArgs.mBootCompleted) { - handleBootCompleted(); - return null; - } - - if (parsedArgs.mAbiListQuery) { - handleAbiListQuery(); - return null; - } - - if (parsedArgs.mPidQuery) { - handlePidQuery(); - return null; - } + Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { + ZygoteArguments parsedArgs; + + try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { + while (true) { + try { + parsedArgs = ZygoteArguments.getInstance(argBuffer); + // Keep argBuffer around, since we need it to fork. + } catch (IOException ex) { + throw new IllegalStateException("IOException on command socket", ex); + } + if (parsedArgs == null) { + isEof = true; + return null; + } - if (parsedArgs.mUsapPoolStatusSpecified) { - return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled); - } + int pid; + FileDescriptor childPipeFd = null; + FileDescriptor serverPipeFd = null; - if (parsedArgs.mPreloadDefault) { - handlePreload(); - return null; - } + if (parsedArgs.mBootCompleted) { + handleBootCompleted(); + return null; + } - if (parsedArgs.mPreloadPackage != null) { - handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, - parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); - return null; - } + if (parsedArgs.mAbiListQuery) { + handleAbiListQuery(); + return null; + } - if (canPreloadApp() && parsedArgs.mPreloadApp != null) { - byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); - Parcel appInfoParcel = Parcel.obtain(); - appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); - appInfoParcel.setDataPosition(0); - ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); - appInfoParcel.recycle(); - if (appInfo != null) { - handlePreloadApp(appInfo); - } else { - throw new IllegalArgumentException("Failed to deserialize --preload-app"); - } - return null; - } + if (parsedArgs.mPidQuery) { + handlePidQuery(); + return null; + } - if (parsedArgs.mApiDenylistExemptions != null) { - return handleApiDenylistExemptions(zygoteServer, parsedArgs.mApiDenylistExemptions); - } + if (parsedArgs.mUsapPoolStatusSpecified) { + // Handle this once we've released the argBuffer, to avoid opening a second one. + break; + } - if (parsedArgs.mHiddenApiAccessLogSampleRate != -1 - || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) { - return handleHiddenApiAccessLogSampleRate(zygoteServer, - parsedArgs.mHiddenApiAccessLogSampleRate, - parsedArgs.mHiddenApiAccessStatslogSampleRate); - } + if (parsedArgs.mPreloadDefault) { + handlePreload(); + return null; + } - if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { - throw new ZygoteSecurityException("Client may not specify capabilities: " - + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) - + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); - } + if (parsedArgs.mPreloadPackage != null) { + handlePreloadPackage(parsedArgs.mPreloadPackage, + parsedArgs.mPreloadPackageLibs, + parsedArgs.mPreloadPackageLibFileName, + parsedArgs.mPreloadPackageCacheKey); + return null; + } - Zygote.applyUidSecurityPolicy(parsedArgs, peer); - Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); + if (canPreloadApp() && parsedArgs.mPreloadApp != null) { + byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); + Parcel appInfoParcel = Parcel.obtain(); + appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); + appInfoParcel.setDataPosition(0); + ApplicationInfo appInfo = + ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); + appInfoParcel.recycle(); + if (appInfo != null) { + handlePreloadApp(appInfo); + } else { + throw new IllegalArgumentException("Failed to deserialize --preload-app"); + } + return null; + } - Zygote.applyDebuggerSystemProperty(parsedArgs); - Zygote.applyInvokeWithSystemProperty(parsedArgs); + if (parsedArgs.mApiDenylistExemptions != null) { + return handleApiDenylistExemptions(zygoteServer, + parsedArgs.mApiDenylistExemptions); + } - int[][] rlimits = null; + if (parsedArgs.mHiddenApiAccessLogSampleRate != -1 + || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) { + return handleHiddenApiAccessLogSampleRate(zygoteServer, + parsedArgs.mHiddenApiAccessLogSampleRate, + parsedArgs.mHiddenApiAccessStatslogSampleRate); + } - if (parsedArgs.mRLimits != null) { - rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); - } + if (parsedArgs.mPermittedCapabilities != 0 + || parsedArgs.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) + + ", effective=0x" + + Long.toHexString(parsedArgs.mEffectiveCapabilities)); + } - int[] fdsToIgnore = null; + Zygote.applyUidSecurityPolicy(parsedArgs, peer); + Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); - if (parsedArgs.mInvokeWith != null) { - try { - FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); - childPipeFd = pipeFds[1]; - serverPipeFd = pipeFds[0]; - Os.fcntlInt(childPipeFd, F_SETFD, 0); - fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; - } catch (ErrnoException errnoEx) { - throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx); - } - } + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); - /* - * In order to avoid leaking descriptors to the Zygote child, - * the native code must close the two Zygote socket descriptors - * in the child process before it switches from Zygote-root to - * the UID and privileges of the application being launched. - * - * In order to avoid "bad file descriptor" errors when the - * two LocalSocket objects are closed, the Posix file - * descriptors are released via a dup2() call which closes - * the socket and substitutes an open descriptor to /dev/null. - */ + int[][] rlimits = null; - int [] fdsToClose = { -1, -1 }; + if (parsedArgs.mRLimits != null) { + rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); + } - FileDescriptor fd = mSocket.getFileDescriptor(); + int[] fdsToIgnore = null; + + if (parsedArgs.mInvokeWith != null) { + try { + FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); + childPipeFd = pipeFds[1]; + serverPipeFd = pipeFds[0]; + Os.fcntlInt(childPipeFd, F_SETFD, 0); + fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; + } catch (ErrnoException errnoEx) { + throw new IllegalStateException("Unable to set up pipe for invoke-with", + errnoEx); + } + } - if (fd != null) { - fdsToClose[0] = fd.getInt$(); - } + /* + * In order to avoid leaking descriptors to the Zygote child, + * the native code must close the two Zygote socket descriptors + * in the child process before it switches from Zygote-root to + * the UID and privileges of the application being launched. + * + * In order to avoid "bad file descriptor" errors when the + * two LocalSocket objects are closed, the Posix file + * descriptors are released via a dup2() call which closes + * the socket and substitutes an open descriptor to /dev/null. + */ - fd = zygoteServer.getZygoteSocketFileDescriptor(); + int [] fdsToClose = { -1, -1 }; - if (fd != null) { - fdsToClose[1] = fd.getInt$(); - } + FileDescriptor fd = mSocket.getFileDescriptor(); - pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, - parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, - parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, - parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp, - parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList, - parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs); + if (fd != null) { + fdsToClose[0] = fd.getInt$(); + } - try { - if (pid == 0) { - // in child - zygoteServer.setForkChild(); + FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor(); - zygoteServer.closeServerSocket(); - IoUtils.closeQuietly(serverPipeFd); - serverPipeFd = null; + if (zygoteFd != null) { + fdsToClose[1] = zygoteFd.getInt$(); + } - return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); - } else { - // In the parent. A pid < 0 indicates a failure and will be handled in - // handleParentProc. - IoUtils.closeQuietly(childPipeFd); - childPipeFd = null; - handleParentProc(pid, serverPipeFd); - return null; + if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote + || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { + // Continue using old code for now. TODO: Handle these cases in the other path. + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, + parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, + fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, + parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList, + parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs, + parsedArgs.mBindMountAppStorageDirs); + + try { + if (pid == 0) { + // in child + zygoteServer.setForkChild(); + + zygoteServer.closeServerSocket(); + IoUtils.closeQuietly(serverPipeFd); + serverPipeFd = null; + + return handleChildProc(parsedArgs, childPipeFd, + parsedArgs.mStartChildZygote); + } else { + // In the parent. A pid < 0 indicates a failure and will be handled in + // handleParentProc. + IoUtils.closeQuietly(childPipeFd); + childPipeFd = null; + handleParentProc(pid, serverPipeFd); + return null; + } + } finally { + IoUtils.closeQuietly(childPipeFd); + IoUtils.closeQuietly(serverPipeFd); + } + } else { + ZygoteHooks.preFork(); + Runnable result = Zygote.forkSimpleApps(argBuffer, + zygoteServer.getZygoteSocketFileDescriptor(), + peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName); + if (result == null) { + // parent; we finished some number of forks. Result is Boolean. + // We already did the equivalent of handleParentProc(). + ZygoteHooks.postForkCommon(); + // argBuffer contains a command not understood by forksimpleApps. + continue; + } else { + // child; result is a Runnable. + zygoteServer.setForkChild(); + Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary? + return result; + } + } } - } finally { - IoUtils.closeQuietly(childPipeFd); - IoUtils.closeQuietly(serverPipeFd); } + if (parsedArgs.mUsapPoolStatusSpecified) { + // Now that we've released argBuffer: + return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled); + } + throw new AssertionError("Shouldn't get here"); } private void handleAbiListQuery() { @@ -557,7 +590,7 @@ class ZygoteConnection { if (res > 0) { if ((fds[0].revents & POLLIN) != 0) { - // Only read one byte, so as not to block. + // Only read one byte, so as not to block. Really needed? int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1); if (readBytes < 0) { throw new RuntimeException("Some error"); diff --git a/core/java/com/android/internal/os/ZygoteConnectionConstants.java b/core/java/com/android/internal/os/ZygoteConnectionConstants.java index 506e39f30617..0c1cd6de1bb4 100644 --- a/core/java/com/android/internal/os/ZygoteConnectionConstants.java +++ b/core/java/com/android/internal/os/ZygoteConnectionConstants.java @@ -31,9 +31,6 @@ public class ZygoteConnectionConstants { */ public static final int CONNECTION_TIMEOUT_MILLIS = 1000; - /** max number of arguments that a connection can specify */ - public static final int MAX_ZYGOTE_ARGC = 1024; - /** * Wait time for a wrapped app to report back its pid. * diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index f2ed50ef6a07..d5b778e9c9e1 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -65,6 +65,7 @@ import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; import java.io.BufferedReader; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -782,7 +783,13 @@ public class ZygoteInit { int pid; try { - parsedArgs = new ZygoteArguments(args); + ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args); + try { + parsedArgs = ZygoteArguments.getInstance(commandBuffer); + } catch (EOFException e) { + throw new AssertionError("Unexpected argument error for forking system server", e); + } + commandBuffer.close(); Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); @@ -861,7 +868,7 @@ public class ZygoteInit { * into new processes are required to either set the priority to the default value or terminate * before executing any non-system code. The native side of this occurs in SpecializeCommon, * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess, - * ZygoteConnection.handleChildProc, and Zygote.usapMain. + * ZygoteConnection.handleChildProc, and Zygote.childMain. * * @param argv Command line arguments used to specify the Zygote's configuration. */ diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index 585ddf6ddf98..f71b31493035 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -337,7 +337,7 @@ class ZygoteServer { * @param sessionSocketRawFDs Anonymous session sockets that are currently open * @return In the Zygote process this function will always return null; in unspecialized app * processes this function will return a Runnable object representing the new - * application that is passed up from usapMain. + * application that is passed up from childMain (the usap's main wait loop). */ Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) { @@ -420,6 +420,7 @@ class ZygoteServer { * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's * worth at a time. + * @param abiList list of ABIs supported by this zygote. */ Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); @@ -537,22 +538,23 @@ class ZygoteServer { if (pollIndex == 0) { // Zygote server socket - ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); socketFDs.add(newPeer.getFileDescriptor()); - } else if (pollIndex < usapPoolEventFDIndex) { // Session socket accepted from the Zygote server socket try { ZygoteConnection connection = peers.get(pollIndex); - final Runnable command = connection.processOneCommand(this); + boolean multipleForksOK = !isUsapPoolEnabled() + && ZygoteHooks.indefiniteThreadSuspensionOK(); + final Runnable command = + connection.processCommand(this, multipleForksOK); // TODO (chriswailes): Is this extra check necessary? if (mIsForkChild) { // We're in the child. We should always have a command to run at - // this stage if processOneCommand hasn't called "exec". + // this stage if processCommand hasn't called "exec". if (command == null) { throw new IllegalStateException("command == null"); } @@ -565,7 +567,7 @@ class ZygoteServer { } // We don't know whether the remote side of the socket was closed or - // not until we attempt to read from it from processOneCommand. This + // not until we attempt to read from it from processCommand. This // shows up as a regular POLLIN event in our regular processing // loop. if (connection.isClosedByPeer()) { diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index d7b4d78c56cf..d49203c731e9 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -193,34 +193,30 @@ public class MeasuredEnergyStats { return mAccumulatedEnergiesMicroJoules.length; } - // TODO: Get rid of the 'accumulate' boolean. It's always true. /** Updates the given standard energy bucket with the given energy if accumulate is true. */ - public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ, - boolean accumulate) { + public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ) { checkValidStandardBucket(bucket); - updateEntry(bucket, energyDeltaUJ, accumulate); + updateEntry(bucket, energyDeltaUJ); } /** Updates the given custom energy bucket with the given energy if accumulate is true. */ - public void updateCustomBucket(int customBucket, long energyDeltaUJ, boolean accumulate) { + public void updateCustomBucket(int customBucket, long energyDeltaUJ) { if (!isValidCustomBucket(customBucket)) { Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket); return; } final int index = customBucketToIndex(customBucket); - updateEntry(index, energyDeltaUJ, accumulate); + updateEntry(index, energyDeltaUJ); } /** Updates the given index with the given energy if accumulate is true. */ - private void updateEntry(int index, long energyDeltaUJ, boolean accumulate) { - if (accumulate) { - if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { - mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; - } else { - Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " - + getBucketName(index) + " whose value was " - + mAccumulatedEnergiesMicroJoules[index]); - } + private void updateEntry(int index, long energyDeltaUJ) { + if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { + mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; + } else { + Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " + + getBucketName(index) + " whose value was " + + mAccumulatedEnergiesMicroJoules[index]); } } diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING new file mode 100644 index 000000000000..96f31bcbe5b2 --- /dev/null +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] + } + ] +} diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 7edc6c855ec2..fde48e86b0f3 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -67,7 +67,7 @@ interface IStatusBarService void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message, int userId); void onClearAllNotifications(int userId); - void onNotificationClear(String pkg, String tag, int id, int userId, String key, + void onNotificationClear(String pkg, int userId, String key, int dismissalSurface, int dismissalSentiment, in NotificationVisibility nv); void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys, in NotificationVisibility[] noLongerVisibleKeys); diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index f7d440d9fd95..95e0a3b524c5 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -94,6 +94,6 @@ interface ITelephonyRegistry { void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo); void notifyPhysicalChannelConfigForSubscriber(in int subId, in List<PhysicalChannelConfig> configs); - void notifyDataEnabled(boolean enabled, int reason); + void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason); void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in Map allowedNetworkTypeList); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index ef2275d4218c..ab0149fce0a0 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -30,6 +30,7 @@ import android.view.IWindowSession; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.PointerIcon; +import android.view.ScrollCaptureResponse; import android.view.WindowInsets.Type.InsetsType; import android.window.ClientWindowFrames; @@ -160,7 +161,9 @@ public class BaseIWindow extends IWindow.Stub { @Override public void requestScrollCapture(IScrollCaptureCallbacks callbacks) { try { - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse( + new ScrollCaptureResponse.Builder().setDescription("Not Implemented").build()); + } catch (RemoteException ex) { // ignore } diff --git a/core/java/com/android/internal/view/OWNERS b/core/java/com/android/internal/view/OWNERS index 851d1f37522a..eb2478f6550a 100644 --- a/core/java/com/android/internal/view/OWNERS +++ b/core/java/com/android/internal/view/OWNERS @@ -18,3 +18,7 @@ per-file AppearanceRegion = file:/services/core/java/com/android/server/wm/OWNER per-file BaseIWIndow.java = file:/services/core/java/com/android/server/wm/OWNERS per-file RotationPolicy.java = file:/services/core/java/com/android/server/wm/OWNERS per-file WindowManagerPolicyThread.java = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 85fa791b429c..a41511b74a7d 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -16,15 +16,19 @@ package com.android.internal.view; +import android.annotation.UiThread; +import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.HardwareRenderer; import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; -import android.os.Handler; +import android.os.CancellationSignal; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Display.ColorMode; import android.view.ScrollCaptureCallback; import android.view.ScrollCaptureSession; import android.view.Surface; @@ -44,32 +48,42 @@ import java.util.function.Consumer; * @param <V> the specific View subclass handled * @see ScrollCaptureViewHelper */ +@UiThread public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback { - public static final long NO_FRAME_PRODUCED = -1; - private static final String TAG = "ScrollCaptureViewSupport"; private static final boolean WAIT_FOR_ANIMATION = true; private final WeakReference<V> mWeakView; private final ScrollCaptureViewHelper<V> mViewHelper; - private ViewRenderer mRenderer; - private Handler mUiHandler; + private final ViewRenderer mRenderer; private boolean mStarted; private boolean mEnded; ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) { mWeakView = new WeakReference<>(containingView); mRenderer = new ViewRenderer(); - mUiHandler = containingView.getHandler(); + // TODO(b/177649144): provide access to color space from android.media.Image mViewHelper = viewHelper; } - // Base implementation of ScrollCaptureCallback + /** Based on ViewRootImpl#updateColorModeIfNeeded */ + @ColorMode + private static int getColorMode(View containingView) { + Context context = containingView.getContext(); + int colorMode = containingView.getViewRootImpl().mWindowAttributes.getColorMode(); + if (!context.getResources().getConfiguration().isScreenWideColorGamut()) { + colorMode = ActivityInfo.COLOR_MODE_DEFAULT; + } + return colorMode; + } @Override - public final void onScrollCaptureSearch(Consumer<Rect> onReady) { + public final void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); mStarted = false; mEnded = false; @@ -82,7 +96,11 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa } @Override - public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) { + public final void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal, + Runnable onReady) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); mEnded = false; @@ -99,18 +117,22 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa } @Override - public final void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect requestRect) { + public final void onScrollCaptureImageRequest(ScrollCaptureSession session, + CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); if (view == null || !view.isVisibleToUser()) { // Signal to the controller that we have a problem and can't continue. - session.notifyBufferSent(NO_FRAME_PRODUCED, new Rect()); + onComplete.accept(new Rect()); return; } // Ask the view to scroll as needed to bring this area into view. ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect); if (scrollResult.availableArea.isEmpty()) { - session.notifyBufferSent(NO_FRAME_PRODUCED, scrollResult.availableArea); + onComplete.accept(scrollResult.availableArea); return; } view.invalidate(); // don't wait for vsync @@ -121,17 +143,13 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa viewCaptureArea.offset(0, -scrollResult.scrollDelta); if (WAIT_FOR_ANIMATION) { - Log.d(TAG, "render: delaying until animation"); view.postOnAnimation(() -> { - Log.d(TAG, "postOnAnimation(): rendering now"); - long resultFrame = mRenderer.renderView(view, viewCaptureArea); - Log.d(TAG, "notifyBufferSent: " + scrollResult.availableArea); - - session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea)); + mRenderer.renderView(view, viewCaptureArea); + onComplete.accept(new Rect(scrollResult.availableArea)); }); } else { - long resultFrame = mRenderer.renderView(view, viewCaptureArea); - session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea)); + mRenderer.renderView(view, viewCaptureArea); + onComplete.accept(new Rect(scrollResult.availableArea)); } } @@ -239,7 +257,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mCaptureRenderNode.endRecording(); } - public long renderView(View view, Rect sourceRect) { + public void renderView(View view, Rect sourceRect) { if (updateForView(view)) { setupLighting(view); } @@ -258,7 +276,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa switch (request.syncAndDraw()) { case HardwareRenderer.SYNC_OK: case HardwareRenderer.SYNC_REDRAW_REQUESTED: - return frameNumber; + return; case HardwareRenderer.SYNC_FRAME_DROPPED: Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !"); @@ -270,7 +288,6 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !"); break; } - return NO_FRAME_PRODUCED; } public void trimMemory() { @@ -289,5 +306,17 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mTempMatrix.mapRect(mTempRectF); mTempRectF.round(outRect); } + + public void setColorMode(@ColorMode int colorMode) { + mRenderer.setColorMode(colorMode); + } + } + + @Override + public String toString() { + return "ScrollCaptureViewSupport{" + + "view=" + mWeakView.get() + + ", helper=" + mViewHelper + + '}'; } } diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS new file mode 100644 index 000000000000..1262925447b9 --- /dev/null +++ b/core/java/com/android/server/OWNERS @@ -0,0 +1 @@ +per-file SystemConfig.java = toddke@google.com diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 8982519eff96..b40ffb0136f2 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -24,6 +24,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.hardware.SensorPrivacyManager; import android.os.Build; import android.os.CarrierAssociatedAppEntry; import android.os.Environment; @@ -1234,10 +1235,10 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_RAM_NORMAL, 0); } - if (IncrementalManager.isFeatureEnabled()) { + final int incrementalVersion = IncrementalManager.getVersion(); + if (incrementalVersion > 0) { addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); - addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, - IncrementalManager.isV2Available() ? 2 : 1); + addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, incrementalVersion); } if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) { @@ -1252,6 +1253,14 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_CROSS_LAYER_BLUR, 0); } + if (SensorPrivacyManager.USE_MICROPHONE_TOGGLE) { + addFeature(PackageManager.FEATURE_MICROPHONE_TOGGLE, 0); + } + + if (SensorPrivacyManager.USE_CAMERA_TOGGLE) { + addFeature(PackageManager.FEATURE_CAMERA_TOGGLE, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index cf8711b3c037..2287900795a5 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -121,6 +121,7 @@ cc_library_shared { "android_view_PointerIcon.cpp", "android_view_Surface.cpp", "android_view_SurfaceControl.cpp", + "android_view_SurfaceControlFpsListener.cpp", "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", "android_view_TextureView.cpp", @@ -175,6 +176,7 @@ cc_library_shared { "android_hardware_Camera.cpp", "android_hardware_camera2_CameraMetadata.cpp", "android_hardware_camera2_DngCreator.cpp", + "android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp", "android_hardware_camera2_utils_SurfaceUtils.cpp", "android_hardware_display_DisplayManagerGlobal.cpp", "android_hardware_display_DisplayViewport.cpp", @@ -209,6 +211,7 @@ cc_library_shared { "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp", "com_android_internal_os_KernelSingleUidTimeReader.cpp", "com_android_internal_os_Zygote.cpp", + "com_android_internal_os_ZygoteCommandBuffer.cpp", "com_android_internal_os_ZygoteInit.cpp", "hwbinder/EphemeralStorage.cpp", "fd_utils.cpp", @@ -231,6 +234,7 @@ cc_library_shared { "audioclient-types-aidl-cpp", "audioflinger-aidl-cpp", "av-types-aidl-cpp", + "android.hardware.camera.device@3.2", "libandroidicu", "libbpf_android", "libnetdbpf", @@ -260,6 +264,7 @@ cc_library_shared { "libdataloader", "libvulkan", "libETC1", + "libjpeg", "libhardware", "libhardware_legacy", "libselinux", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 38bcc0f4c59e..ddd861380fab 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -74,6 +74,7 @@ extern int register_android_opengl_jni_GLES32(JNIEnv* env); extern int register_android_hardware_Camera(JNIEnv *env); extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env); extern int register_android_hardware_camera2_DngCreator(JNIEnv *env); +extern int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env); extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env); extern int register_android_hardware_display_DisplayManagerGlobal(JNIEnv* env); extern int register_android_hardware_HardwareBuffer(JNIEnv *env); @@ -119,6 +120,7 @@ extern int register_android_view_InputApplicationHandle(JNIEnv* env); extern int register_android_view_InputWindowHandle(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); extern int register_android_view_SurfaceControl(JNIEnv* env); +extern int register_android_view_SurfaceControlFpsListener(JNIEnv* env); extern int register_android_view_SurfaceSession(JNIEnv* env); extern int register_android_view_CompositionSamplingListener(JNIEnv* env); extern int register_android_view_TextureView(JNIEnv* env); @@ -196,6 +198,7 @@ extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env); extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); +extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); @@ -1487,6 +1490,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_InputWindowHandle), REG_JNI(register_android_view_Surface), REG_JNI(register_android_view_SurfaceControl), + REG_JNI(register_android_view_SurfaceControlFpsListener), REG_JNI(register_android_view_SurfaceSession), REG_JNI(register_android_view_CompositionSamplingListener), REG_JNI(register_android_view_TextureView), @@ -1528,11 +1532,13 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_Zygote), + REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_DngCreator), + REG_JNI(register_android_hardware_camera2_impl_CameraExtensionJpegProcessor), REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils), REG_JNI(register_android_hardware_display_DisplayManagerGlobal), REG_JNI(register_android_hardware_HardwareBuffer), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 35d1d7bd7946..19c6a625646e 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -42,6 +42,9 @@ per-file android_os_HwParcel* = file:platform/system/libhwbinder:/OWNERS per-file android_os_HwRemoteBinder* = file:platform/system/libhwbinder:/OWNERS per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS +# Sensor +per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com + per-file *Zygote* = file:/ZYGOTE_OWNERS per-file Android.bp = file:platform/build/soong:/OWNERS per-file android_animation_* = file:/core/java/android/animation/OWNERS diff --git a/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp b/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp new file mode 100644 index 000000000000..139075907bf3 --- /dev/null +++ b/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp @@ -0,0 +1,629 @@ +/* + * 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 <array> +#include <cstring> +#include <cstdio> +#include <inttypes.h> +#include <memory.h> +#include <vector> + +#include <setjmp.h> + +#include <android/hardware/camera/device/3.2/types.h> + +#include "core_jni_helpers.h" +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +#define CAMERA_PROCESSOR_CLASS_NAME "android/hardware/camera2/impl/CameraExtensionJpegProcessor" + +extern "C" { +#include "jpeglib.h" +} + +using namespace std; +using namespace android; + +using android::hardware::camera::device::V3_2::CameraBlob; +using android::hardware::camera::device::V3_2::CameraBlobId; + +class Transform; +struct Plane; + +inline int sgn(int val) { return (0 < val) - (val < 0); } + +inline int min(int a, int b) { return a < b ? a : b; } + +inline int max(int a, int b) { return a > b ? a : b; } + +/** + * Represents a combined cropping and rotation transformation. + * + * The transformation maps the coordinates (mOrigX, mOrigY) and (mOneX, mOneY) + * in the input image to the origin and (mOutputWidth, mOutputHeight) + * respectively. + */ +class Transform { + public: + Transform(int origX, int origY, int oneX, int oneY); + + static Transform forCropFollowedByRotation(int cropLeft, int cropTop, + int cropRight, int cropBottom, int rot90); + + inline int getOutputWidth() const { return mOutputWidth; } + + inline int getOutputHeight() const { return mOutputHeight; } + + bool operator==(const Transform& other) const; + + /** + * Transforms the input coordinates. Coordinates outside the cropped region + * are clamped to valid values. + */ + void map(int x, int y, int* outX, int* outY) const; + + private: + int mOutputWidth; + int mOutputHeight; + + // The coordinates of the point to map the origin to. + const int mOrigX, mOrigY; + // The coordinates of the point to map the point (getOutputWidth(), + // getOutputHeight()) to. + const int mOneX, mOneY; + + // A matrix for the rotational component. + int mMat00, mMat01; + int mMat10, mMat11; +}; + +/** + * Represents a model for accessing pixel data for a single plane of an image. + * Note that the actual data is not owned by this class, and the underlying + * data does not need to be stored in separate planes. + */ +struct Plane { + // The dimensions of this plane of the image + int width; + int height; + + // A pointer to raw pixel data + const unsigned char* data; + // The difference in address between consecutive pixels in the same row + int pixelStride; + // The difference in address between the start of consecutive rows + int rowStride; +}; + +/** + * Provides an interface for simultaneously reading a certain number of rows of + * an image plane as contiguous arrays, suitable for use with libjpeg. + */ +template <unsigned int ROWS> +class RowIterator { + public: + /** + * Creates a new RowIterator which will crop and rotate with the given + * transform. + * + * @param plane the plane to iterate over + * @param transform the transformation to map output values into the + * coordinate space of the plane + * @param rowLength the length of the rows returned via LoadAt(). If this is + * longer than the width of the output (after applying the transform), then + * the right-most value is repeated. + */ + inline RowIterator(Plane plane, Transform transform, int rowLength); + + /** + * Returns an array of pointers into consecutive rows of contiguous image + * data starting at y. That is, samples within each row are contiguous. + * However, the individual arrays pointed-to may be separate. + * When the end of the image is reached, the last row of the image is + * repeated. + * The returned pointers are valid until the next call to loadAt(). + */ + inline const std::array<unsigned char*, ROWS> loadAt(int baseY); + + private: + Plane mPlane; + Transform mTransform; + // The length of a row, with padding to the next multiple of 64. + int mPaddedRowLength; + std::vector<unsigned char> mBuffer; +}; + +template <unsigned int ROWS> +RowIterator<ROWS>::RowIterator(Plane plane, Transform transform, + int rowLength) + : mPlane(plane), mTransform(transform) { + mPaddedRowLength = rowLength; + mBuffer = std::vector<unsigned char>(rowLength * ROWS); +} + +template <unsigned int ROWS> +const std::array<unsigned char*, ROWS> RowIterator<ROWS>::loadAt(int baseY) { + std::array<unsigned char*, ROWS> bufPtrs; + for (unsigned int i = 0; i < ROWS; i++) { + bufPtrs[i] = &mBuffer[mPaddedRowLength * i]; + } + + if (mPlane.width == 0 || mPlane.height == 0) { + return bufPtrs; + } + + for (unsigned int i = 0; i < ROWS; i++) { + int y = i + baseY; + y = min(y, mTransform.getOutputHeight() - 1); + + int output_width = mPaddedRowLength; + output_width = min(output_width, mTransform.getOutputWidth()); + output_width = min(output_width, mPlane.width); + + // Each row in the output image will be copied into buf_ by gathering pixels + // along an axis-aligned line in the plane. + // The line is defined by (startX, startY) -> (endX, endY), computed via the + // current Transform. + int startX; + int startY; + mTransform.map(0, y, &startX, &startY); + + int endX; + int endY; + mTransform.map(output_width - 1, y, &endX, &endY); + + // Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane. + startX = min(startX, mPlane.width - 1); + startY = min(startY, mPlane.height - 1); + endX = min(endX, mPlane.width - 1); + endY = min(endY, mPlane.height - 1); + startX = max(startX, 0); + startY = max(startY, 0); + endX = max(endX, 0); + endY = max(endY, 0); + + // To reduce work inside the copy-loop, precompute the start, end, and + // stride relating the values to be gathered from mPlane into buf + // for this particular scan-line. + int dx = sgn(endX - startX); + int dy = sgn(endY - startY); + if (!(dx == 0 || dy == 0)) { + ALOGE("%s: Unexpected bounds: %dx%d %dx%d!", __FUNCTION__, startX, endX, startY, endY); + return bufPtrs; + } + + // The index into mPlane.data of (startX, startY) + int plane_start = startX * mPlane.pixelStride + startY * mPlane.rowStride; + // The index into mPlane.data of (endX, endY) + int plane_end = endX * mPlane.pixelStride + endY * mPlane.rowStride; + // The stride, in terms of indices in plane_data, required to enumerate the + // samples between the start and end points. + int stride = dx * mPlane.pixelStride + dy * mPlane.rowStride; + // In the degenerate-case of a 1x1 plane, startX and endX are equal, so + // stride would be 0, resulting in an infinite-loop. To avoid this case, + // use a stride of at-least 1. + if (stride == 0) { + stride = 1; + } + + int outX = 0; + for (int idx = plane_start; idx >= min(plane_start, plane_end) && + idx <= max(plane_start, plane_end); idx += stride) { + bufPtrs[i][outX] = mPlane.data[idx]; + outX++; + } + + // Fill the remaining right-edge of the buffer by extending the last + // value. + unsigned char right_padding_value = bufPtrs[i][outX - 1]; + for (; outX < mPaddedRowLength; outX++) { + bufPtrs[i][outX] = right_padding_value; + } + } + + return bufPtrs; +} + +template <typename T> +void safeDelete(T& t) { + delete t; + t = nullptr; +} + +template <typename T> +void safeDeleteArray(T& t) { + delete[] t; + t = nullptr; +} + +Transform::Transform(int origX, int origY, int oneX, int oneY) + : mOrigX(origX), mOrigY(origY), mOneX(oneX), mOneY(oneY) { + if (origX == oneX || origY == oneY) { + // Handle the degenerate case of cropping to a 0x0 rectangle. + mMat00 = 0; + mMat01 = 0; + mMat10 = 0; + mMat11 = 0; + return; + } + + if (oneX > origX && oneY > origY) { + // 0-degree rotation + mMat00 = 1; + mMat01 = 0; + mMat10 = 0; + mMat11 = 1; + mOutputWidth = abs(oneX - origX); + mOutputHeight = abs(oneY - origY); + } else if (oneX < origX && oneY > origY) { + // 90-degree CCW rotation + mMat00 = 0; + mMat01 = -1; + mMat10 = 1; + mMat11 = 0; + mOutputWidth = abs(oneY - origY); + mOutputHeight = abs(oneX - origX); + } else if (oneX > origX && oneY < origY) { + // 270-degree CCW rotation + mMat00 = 0; + mMat01 = 1; + mMat10 = -1; + mMat11 = 0; + mOutputWidth = abs(oneY - origY); + mOutputHeight = abs(oneX - origX);; + } else if (oneX < origX && oneY < origY) { + // 180-degree CCW rotation + mMat00 = -1; + mMat01 = 0; + mMat10 = 0; + mMat11 = -1; + mOutputWidth = abs(oneX - origX); + mOutputHeight = abs(oneY - origY); + } +} + +Transform Transform::forCropFollowedByRotation(int cropLeft, int cropTop, int cropRight, + int cropBottom, int rot90) { + // The input crop-region excludes cropRight and cropBottom, so transform the + // crop rect such that it defines the entire valid region of pixels + // inclusively. + cropRight -= 1; + cropBottom -= 1; + + int cropXLow = min(cropLeft, cropRight); + int cropYLow = min(cropTop, cropBottom); + int cropXHigh = max(cropLeft, cropRight); + int cropYHigh = max(cropTop, cropBottom); + rot90 %= 4; + if (rot90 == 0) { + return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1); + } else if (rot90 == 1) { + return Transform(cropXHigh, cropYLow, cropXLow - 1, cropYHigh + 1); + } else if (rot90 == 2) { + return Transform(cropXHigh, cropYHigh, cropXLow - 1, cropYLow - 1); + } else if (rot90 == 3) { + return Transform(cropXLow, cropYHigh, cropXHigh + 1, cropYLow - 1); + } + // Impossible case. + return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1); +} + +bool Transform::operator==(const Transform& other) const { + return other.mOrigX == mOrigX && // + other.mOrigY == mOrigY && // + other.mOneX == mOneX && // + other.mOneY == mOneY; +} + +/** + * Transforms the input coordinates. Coordinates outside the cropped region + * are clamped to valid values. + */ +void Transform::map(int x, int y, int* outX, int* outY) const { + x = max(x, 0); + y = max(y, 0); + x = min(x, getOutputWidth() - 1); + y = min(y, getOutputHeight() - 1); + *outX = x * mMat00 + y * mMat01 + mOrigX; + *outY = x * mMat10 + y * mMat11 + mOrigY; +} + +int compress(int img_width, int img_height, RowIterator<16>& y_row_generator, + RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator, + unsigned char* out_buf, size_t out_buf_capacity, std::function<void(size_t)> flush, + int quality) { + // libjpeg requires the use of setjmp/longjmp to recover from errors. Since + // this doesn't play well with RAII, we must use pointers and manually call + // delete. See POSIX documentation for longjmp() for details on why the + // volatile keyword is necessary. + volatile jpeg_compress_struct cinfov; + + jpeg_compress_struct& cinfo = + *const_cast<struct jpeg_compress_struct*>(&cinfov); + + JSAMPROW* volatile yArr = nullptr; + JSAMPROW* volatile cbArr = nullptr; + JSAMPROW* volatile crArr = nullptr; + + JSAMPARRAY imgArr[3]; + + // Error handling + + struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; + } err; + + cinfo.err = jpeg_std_error(&err.pub); + + // Default error_exit will call exit(), so override + // to return control via setjmp/longjmp. + err.pub.error_exit = [](j_common_ptr cinfo) { + my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err); + + (*cinfo->err->output_message)(cinfo); + + // Return control to the setjmp point (see call to setjmp()). + longjmp(myerr->setjmp_buffer, 1); + }; + + cinfo.err = (struct jpeg_error_mgr*)&err; + + // Set the setjmp point to return to in case of error. + if (setjmp(err.setjmp_buffer)) { + // If libjpeg hits an error, control will jump to this point (see call to + // longjmp()). + jpeg_destroy_compress(&cinfo); + + safeDeleteArray(yArr); + safeDeleteArray(cbArr); + safeDeleteArray(crArr); + + return -1; + } + + // Create jpeg compression context + jpeg_create_compress(&cinfo); + + // Stores data needed by our c-style callbacks into libjpeg + struct ClientData { + unsigned char* out_buf; + size_t out_buf_capacity; + std::function<void(size_t)> flush; + int totalOutputBytes; + } clientData{out_buf, out_buf_capacity, flush, 0}; + + cinfo.client_data = &clientData; + + // Initialize destination manager + jpeg_destination_mgr dest; + + dest.init_destination = [](j_compress_ptr cinfo) { + ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data); + + cinfo->dest->next_output_byte = cdata.out_buf; + cinfo->dest->free_in_buffer = cdata.out_buf_capacity; + }; + + dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean { + ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data); + + size_t numBytesInBuffer = cdata.out_buf_capacity; + cdata.flush(numBytesInBuffer); + cdata.totalOutputBytes += numBytesInBuffer; + + // Reset the buffer + cinfo->dest->next_output_byte = cdata.out_buf; + cinfo->dest->free_in_buffer = cdata.out_buf_capacity; + + return true; + }; + + dest.term_destination = [](j_compress_ptr cinfo __unused) { + // do nothing to terminate the output buffer + }; + + cinfo.dest = &dest; + + // Set jpeg parameters + cinfo.image_width = img_width; + cinfo.image_height = img_height; + cinfo.input_components = 3; + + // Set defaults based on the above values + jpeg_set_defaults(&cinfo); + + jpeg_set_quality(&cinfo, quality, true); + + cinfo.dct_method = JDCT_IFAST; + + cinfo.raw_data_in = true; + + jpeg_set_colorspace(&cinfo, JCS_YCbCr); + + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + jpeg_start_compress(&cinfo, true); + + yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE]; + cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE]; + crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE]; + + imgArr[0] = const_cast<JSAMPARRAY>(yArr); + imgArr[1] = const_cast<JSAMPARRAY>(cbArr); + imgArr[2] = const_cast<JSAMPARRAY>(crArr); + + for (int y = 0; y < img_height; y += DCTSIZE * 2) { + std::array<unsigned char*, 16> yData = y_row_generator.loadAt(y); + std::array<unsigned char*, 8> cbData = cb_row_generator.loadAt(y / 2); + std::array<unsigned char*, 8> crData = cr_row_generator.loadAt(y / 2); + + for (int row = 0; row < DCTSIZE * 2; row++) { + yArr[row] = yData[row]; + } + for (int row = 0; row < DCTSIZE; row++) { + cbArr[row] = cbData[row]; + crArr[row] = crData[row]; + } + + jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2); + } + + jpeg_finish_compress(&cinfo); + + int numBytesInBuffer = cinfo.dest->next_output_byte - out_buf; + + flush(numBytesInBuffer); + + clientData.totalOutputBytes += numBytesInBuffer; + + safeDeleteArray(yArr); + safeDeleteArray(cbArr); + safeDeleteArray(crArr); + + jpeg_destroy_compress(&cinfo); + + return clientData.totalOutputBytes; +} + +int compress( + /** Input image dimensions */ + int width, int height, + /** Y Plane */ + unsigned char* yBuf, int yPStride, int yRStride, + /** Cb Plane */ + unsigned char* cbBuf, int cbPStride, int cbRStride, + /** Cr Plane */ + unsigned char* crBuf, int crPStride, int crRStride, + /** Output */ + unsigned char* outBuf, size_t outBufCapacity, + /** Jpeg compression parameters */ + int quality, + /** Crop */ + int cropLeft, int cropTop, int cropRight, int cropBottom, + /** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree + * rotation. */ + int rot90) { + int finalWidth; + int finalHeight; + finalWidth = cropRight - cropLeft; + finalHeight = cropBottom - cropTop; + + rot90 %= 4; + // for 90 and 270-degree rotations, flip the final width and height + if (rot90 == 1) { + finalWidth = cropBottom - cropTop; + finalHeight = cropRight - cropLeft; + } else if (rot90 == 3) { + finalWidth = cropBottom - cropTop; + finalHeight = cropRight - cropLeft; + } + + const Plane yP = {width, height, yBuf, yPStride, yRStride}; + const Plane cbP = {width / 2, height / 2, cbBuf, cbPStride, cbRStride}; + const Plane crP = {width / 2, height / 2, crBuf, crPStride, crRStride}; + + auto flush = [](size_t numBytes __unused) { + // do nothing + }; + + // Round up to the nearest multiple of 64. + int y_row_length = (finalWidth + 16 + 63) & ~63; + int cb_row_length = (finalWidth / 2 + 16 + 63) & ~63; + int cr_row_length = (finalWidth / 2 + 16 + 63) & ~63; + + Transform yTrans = Transform::forCropFollowedByRotation( + cropLeft, cropTop, cropRight, cropBottom, rot90); + + Transform chromaTrans = Transform::forCropFollowedByRotation( + cropLeft / 2, cropTop / 2, cropRight / 2, cropBottom / 2, rot90); + + RowIterator<16> yIter(yP, yTrans, y_row_length); + RowIterator<8> cbIter(cbP, chromaTrans, cb_row_length); + RowIterator<8> crIter(crP, chromaTrans, cr_row_length); + + return compress(finalWidth, finalHeight, yIter, cbIter, crIter, outBuf, outBufCapacity, flush, + quality); +} + +extern "C" { + +static jint CameraExtensionJpegProcessor_compressJpegFromYUV420p( + JNIEnv* env, jclass clazz __unused, + /** Input image dimensions */ + jint width, jint height, + /** Y Plane */ + jobject yBuf, jint yPStride, jint yRStride, + /** Cb Plane */ + jobject cbBuf, jint cbPStride, jint cbRStride, + /** Cr Plane */ + jobject crBuf, jint crPStride, jint crRStride, + /** Output */ + jobject outBuf, jint outBufCapacity, + /** Jpeg compression parameters */ + jint quality, + /** Crop */ + jint cropLeft, jint cropTop, jint cropRight, jint cropBottom, + /** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree + * rotation. */ + jint rot90) { + jbyte* y = (jbyte*)env->GetDirectBufferAddress(yBuf); + jbyte* cb = (jbyte*)env->GetDirectBufferAddress(cbBuf); + jbyte* cr = (jbyte*)env->GetDirectBufferAddress(crBuf); + jbyte* out = (jbyte*)env->GetDirectBufferAddress(outBuf); + + size_t actualJpegSize = compress(width, height, + (unsigned char*)y, yPStride, yRStride, + (unsigned char*)cb, cbPStride, cbRStride, + (unsigned char*)cr, crPStride, crRStride, + (unsigned char*)out, (size_t)outBufCapacity, + quality, cropLeft, cropTop, cropRight, cropBottom, rot90); + + size_t finalJpegSize = actualJpegSize + sizeof(CameraBlob); + if (finalJpegSize > outBufCapacity) { + ALOGE("%s: Final jpeg buffer %zu not large enough for the jpeg blob header with "\ + "capacity %d", __FUNCTION__, finalJpegSize, outBufCapacity); + return actualJpegSize; + } + + int8_t* header = static_cast<int8_t *> (out) + + (outBufCapacity - sizeof(CameraBlob)); + CameraBlob *blob = reinterpret_cast<CameraBlob *> (header); + blob->blobId = CameraBlobId::JPEG; + blob->blobSize = actualJpegSize; + + return actualJpegSize; +} + +} // extern "C" + +static const JNINativeMethod gCameraExtensionJpegProcessorMethods[] = { + {"compressJpegFromYUV420pNative", + "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIIIIII)I", + (void*)CameraExtensionJpegProcessor_compressJpegFromYUV420p}}; + +// Get all the required offsets in java class and register native functions +int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env) { + // Register native functions + return RegisterMethodsOrDie(env, CAMERA_PROCESSOR_CLASS_NAME, + gCameraExtensionJpegProcessorMethods, NELEM(gCameraExtensionJpegProcessorMethods)); +} diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index cbf4481bd2f1..451ea93349f7 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -27,7 +27,6 @@ #include <android-base/chrono_utils.h> #include <android/graphics/region.h> #include <android/gui/BnScreenCaptureListener.h> -#include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_hardware_HardwareBuffer.h> @@ -1023,24 +1022,16 @@ static jobject convertDeviceProductInfoToJavaObject( } else { LOG_FATAL("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate"); } - jint connectionToSinkType; - // Relative address maps to HDMI physical address. All addresses are 4 digits long allowing - // for a 5–device-deep hierarchy. For more information, refer: - // Section 8.7 - Physical Address of HDMI Specification Version 1.3a - using android::hardware::display::IDeviceProductInfoConstants; - if (info->relativeAddress.size() != 4) { - connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN; - } else if (info->relativeAddress[0] == 0) { - connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN; - } else if (info->relativeAddress[1] == 0) { - connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_DIRECT; - } else { - connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_TRANSITIVE; + auto relativeAddress = env->NewIntArray(info->relativeAddress.size()); + auto relativeAddressData = env->GetIntArrayElements(relativeAddress, nullptr); + for (int i = 0; i < info->relativeAddress.size(); i++) { + relativeAddressData[i] = info->relativeAddress[i]; } + env->ReleaseIntArrayElements(relativeAddress, relativeAddressData, 0); return env->NewObject(gDeviceProductInfoClassInfo.clazz, gDeviceProductInfoClassInfo.ctor, name, manufacturerPnpId, productId, modelYear, manufactureDate, - connectionToSinkType); + relativeAddress); } static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) { @@ -1979,7 +1970,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) "Ljava/lang/String;" "Ljava/lang/Integer;" "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;" - "I)V"); + "[I)V"); jclass deviceProductInfoManufactureDateClazz = FindClassOrDie(env, "android/hardware/display/DeviceProductInfo$ManufactureDate"); diff --git a/core/jni/android_view_SurfaceControlFpsListener.cpp b/core/jni/android_view_SurfaceControlFpsListener.cpp new file mode 100644 index 000000000000..6fa12e510459 --- /dev/null +++ b/core/jni/android_view_SurfaceControlFpsListener.cpp @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceControlFpsListener" + +#include <android/gui/BnFpsListener.h> +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <gui/ISurfaceComposer.h> +#include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include "android_util_Binder.h" +#include "core_jni_helpers.h" + +namespace android { + +namespace { + +struct { + jclass mClass; + jmethodID mDispatchOnFpsReported; +} gListenerClassInfo; + +struct SurfaceControlFpsListener : public gui::BnFpsListener { + SurfaceControlFpsListener(JNIEnv* env, jobject listener) + : mListener(env->NewWeakGlobalRef(listener)) {} + + binder::Status onFpsReported(float fps) override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onFpsReported."); + + jobject listener = env->NewGlobalRef(mListener); + if (listener == NULL) { + // Weak reference went out of scope + return binder::Status::ok(); + } + env->CallStaticVoidMethod(gListenerClassInfo.mClass, + gListenerClassInfo.mDispatchOnFpsReported, listener, + static_cast<jfloat>(fps)); + env->DeleteGlobalRef(listener); + + if (env->ExceptionCheck()) { + ALOGE("SurfaceControlFpsListener.onFpsReported() failed."); + LOGE_EX(env); + env->ExceptionClear(); + } + return binder::Status::ok(); + } + +protected: + virtual ~SurfaceControlFpsListener() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mListener); + } + +private: + jweak mListener; +}; + +jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) { + SurfaceControlFpsListener* listener = new SurfaceControlFpsListener(env, obj); + listener->incStrong((void*)nativeCreate); + return reinterpret_cast<jlong>(listener); +} + +void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { + SurfaceControlFpsListener* listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); + listener->decStrong((void*)nativeCreate); +} + +void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr, jlong layerObj) { + sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); + auto layer = reinterpret_cast<SurfaceControl*>(layerObj); + sp<IBinder> layerHandle = layer != nullptr ? layer->getHandle() : nullptr; + if (SurfaceComposerClient::addFpsListener(layerHandle, listener) != OK) { + constexpr auto error_msg = "Couldn't addFpsListener"; + ALOGE(error_msg); + jniThrowRuntimeException(env, error_msg); + } +} + +void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { + sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); + + if (SurfaceComposerClient::removeFpsListener(listener) != OK) { + constexpr auto error_msg = "Couldn't removeFpsListener"; + ALOGE(error_msg); + jniThrowRuntimeException(env, error_msg); + } +} + +const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"nativeCreate", "(Landroid/view/SurfaceControlFpsListener;)J", (void*)nativeCreate}, + {"nativeDestroy", "(J)V", (void*)nativeDestroy}, + {"nativeRegister", "(JJ)V", (void*)nativeRegister}, + {"nativeUnregister", "(J)V", (void*)nativeUnregister}}; + +} // namespace + +int register_android_view_SurfaceControlFpsListener(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/SurfaceControlFpsListener", gMethods, + NELEM(gMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz = env->FindClass("android/view/SurfaceControlFpsListener"); + gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); + gListenerClassInfo.mDispatchOnFpsReported = + env->GetStaticMethodID(clazz, "dispatchOnFpsReported", + "(Landroid/view/SurfaceControlFpsListener;F)V"); + return 0; +} + +} // namespace android diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index e8017253fc29..c9062d8a50bc 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "Zygote" #define ATRACE_TAG ATRACE_TAG_DALVIK +#include "com_android_internal_os_Zygote.h" + #include <async_safe/log.h> // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc @@ -91,19 +93,6 @@ #include "nativebridge/native_bridge.h" -/* Functions in the callchain during the fork shall not be protected with - Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */ -#ifdef __ARM_FEATURE_PAC_DEFAULT -#ifdef __ARM_FEATURE_BTI_DEFAULT -#define NO_PAC_FUNC __attribute__((target("branch-protection=bti"))) -#else -#define NO_PAC_FUNC __attribute__((target("branch-protection=none"))) -#endif /* __ARM_FEATURE_BTI_DEFAULT */ -#else /* !__ARM_FEATURE_PAC_DEFAULT */ -#define NO_PAC_FUNC -#endif /* __ARM_FEATURE_PAC_DEFAULT */ - - namespace { // TODO (chriswailes): Add a function to initialize native Zygote data. @@ -118,8 +107,7 @@ using android::base::StringPrintf; using android::base::WriteStringToFile; using android::base::GetBoolProperty; -#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ - append(StringPrintf(__VA_ARGS__)) +using android::zygote::ZygoteFailure; // This type is duplicated in fd_utils.h typedef const std::function<void(std::string)>& fail_fn_t; @@ -214,7 +202,7 @@ class UsapTableEntry { static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1}; std::atomic<EntryStorage> mStorage; - static_assert(decltype(mStorage)::is_always_lock_free); + static_assert(decltype(mStorage)::is_always_lock_free); // Accessed from signal handler. public: constexpr UsapTableEntry() : mStorage(INVALID_ENTRY_VALUE) {} @@ -917,36 +905,6 @@ void SetThreadName(const std::string& thread_name) { } /** - * A failure function used to report fatal errors to the managed runtime. This - * function is often curried with the process name information and then passed - * to called functions. - * - * @param env Managed runtime environment - * @param process_name A native representation of the process name - * @param managed_process_name A managed representation of the process name - * @param msg The error message to be reported - */ -[[noreturn]] -static void ZygoteFailure(JNIEnv* env, - const char* process_name, - jstring managed_process_name, - const std::string& msg) { - std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr; - if (managed_process_name != nullptr) { - scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name)); - if (scoped_managed_process_name_ptr->c_str() != nullptr) { - process_name = scoped_managed_process_name_ptr->c_str(); - } - } - - const std::string& error_msg = - (process_name == nullptr) ? msg : StringPrintf("(%s) %s", process_name, msg.c_str()); - - env->FatalError(error_msg.c_str()); - __builtin_unreachable(); -} - -/** * A helper method for converting managed strings to native strings. A fatal * error is generated if a problem is encountered in extracting a non-null * string. @@ -1073,86 +1031,6 @@ static void PAuthKeyChange(JNIEnv* env) { #endif } -// Utility routine to fork a process from the zygote. -NO_PAC_FUNC -static pid_t ForkCommon(JNIEnv* env, bool is_system_server, - const std::vector<int>& fds_to_close, - const std::vector<int>& fds_to_ignore, - bool is_priority_fork) { - SetSignalHandlers(); - - // Curry a failure function. - auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", - nullptr, _1); - - // Temporarily block SIGCHLD during forks. The SIGCHLD handler might - // log, which would result in the logging FDs we close being reopened. - // This would cause failures because the FDs are not allowlisted. - // - // Note that the zygote process is single threaded at this point. - BlockSignal(SIGCHLD, fail_fn); - - // Close any logging related FDs before we start evaluating the list of - // file descriptors. - __android_log_close(); - AStatsSocket_close(); - - // If this is the first fork for this zygote, create the open FD table. If - // it isn't, we just need to check whether the list of open files has changed - // (and it shouldn't in the normal case). - if (gOpenFdTable == nullptr) { - gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); - } else { - gOpenFdTable->Restat(fds_to_ignore, fail_fn); - } - - android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); - - // Purge unused native memory in an attempt to reduce the amount of false - // sharing with the child process. By reducing the size of the libc_malloc - // region shared with the child process we reduce the number of pages that - // transition to the private-dirty state when malloc adjusts the meta-data - // on each of the pages it is managing after the fork. - mallopt(M_PURGE, 0); - - pid_t pid = fork(); - - if (pid == 0) { - if (is_priority_fork) { - setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); - } else { - setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); - } - - // The child process. - PAuthKeyChange(env); - PreApplicationInit(); - - // Clean up any descriptors which must be closed immediately - DetachDescriptors(env, fds_to_close, fail_fn); - - // Invalidate the entries in the USAP table. - ClearUsapTable(); - - // Re-open all remaining open file descriptors so that they aren't shared - // with the zygote across a fork. - gOpenFdTable->ReopenOrDetach(fail_fn); - - // Turn fdsan back on. - android_fdsan_set_error_level(fdsan_error_level); - - // Reset the fd to the unsolicited zygote socket - gSystemServerSocketFd = -1; - } else { - ALOGD("Forked child process %d", pid); - } - - // We blocked SIGCHLD prior to a fork, we unblock it here. - UnblockSignal(SIGCHLD, fail_fn); - - return pid; -} - // Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it // from the actual app data directory in data mirror. static bool createAndMountAppData(std::string_view package_name, @@ -1973,9 +1851,10 @@ static void AddUsapTableEntry(pid_t usap_pid, int read_pipe_fd) { static int sUsapTableInsertIndex = 0; int search_index = sUsapTableInsertIndex; - do { if (gUsapTable[search_index].SetIfInvalid(usap_pid, read_pipe_fd)) { + ++gUsapPoolCount; + // Start our next search right after where we finished this one. sUsapTableInsertIndex = (search_index + 1) % gUsapTable.size(); @@ -1993,7 +1872,7 @@ static void AddUsapTableEntry(pid_t usap_pid, int read_pipe_fd) { /** * Invalidates the entry in the USAPTable corresponding to the provided * process ID if it is present. If an entry was removed the USAP pool - * count is decremented. + * count is decremented. May be called from signal handler. * * @param usap_pid Process ID of the USAP entry to invalidate * @return True if an entry was invalidated; false otherwise @@ -2069,6 +1948,121 @@ static void UnmountStorageOnInit(JNIEnv* env) { namespace android { +/** + * A failure function used to report fatal errors to the managed runtime. This + * function is often curried with the process name information and then passed + * to called functions. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param msg The error message to be reported + */ +[[noreturn]] +void zygote::ZygoteFailure(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + const std::string& msg) { + std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr; + if (managed_process_name != nullptr) { + scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name)); + if (scoped_managed_process_name_ptr->c_str() != nullptr) { + process_name = scoped_managed_process_name_ptr->c_str(); + } + } + + const std::string& error_msg = + (process_name == nullptr || process_name[0] == '\0') ? + msg : StringPrintf("(%s) %s", process_name, msg.c_str()); + + env->FatalError(error_msg.c_str()); + __builtin_unreachable(); +} + +// Utility routine to fork a process from the zygote. +NO_PAC_FUNC +pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore, + bool is_priority_fork, + bool purge) { + SetSignalHandlers(); + + // Curry a failure function. + auto fail_fn = std::bind(zygote::ZygoteFailure, env, + is_system_server ? "system_server" : "zygote", + nullptr, _1); + + // Temporarily block SIGCHLD during forks. The SIGCHLD handler might + // log, which would result in the logging FDs we close being reopened. + // This would cause failures because the FDs are not allowlisted. + // + // Note that the zygote process is single threaded at this point. + BlockSignal(SIGCHLD, fail_fn); + + // Close any logging related FDs before we start evaluating the list of + // file descriptors. + __android_log_close(); + AStatsSocket_close(); + + // If this is the first fork for this zygote, create the open FD table. If + // it isn't, we just need to check whether the list of open files has changed + // (and it shouldn't in the normal case). + if (gOpenFdTable == nullptr) { + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); + } else { + gOpenFdTable->Restat(fds_to_ignore, fail_fn); + } + + android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); + + if (purge) { + // Purge unused native memory in an attempt to reduce the amount of false + // sharing with the child process. By reducing the size of the libc_malloc + // region shared with the child process we reduce the number of pages that + // transition to the private-dirty state when malloc adjusts the meta-data + // on each of the pages it is managing after the fork. + mallopt(M_PURGE, 0); + } + + pid_t pid = fork(); + + if (pid == 0) { + if (is_priority_fork) { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); + } else { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); + } + + // The child process. + PAuthKeyChange(env); + PreApplicationInit(); + + // Clean up any descriptors which must be closed immediately + DetachDescriptors(env, fds_to_close, fail_fn); + + // Invalidate the entries in the USAP table. + ClearUsapTable(); + + // Re-open all remaining open file descriptors so that they aren't shared + // with the zygote across a fork. + gOpenFdTable->ReopenOrDetach(fail_fn); + + // Turn fdsan back on. + android_fdsan_set_error_level(fdsan_error_level); + + // Reset the fd to the unsolicited zygote socket + gSystemServerSocketFd = -1; + } else { + ALOGD("Forked child process %d", pid); + } + + // We blocked SIGCHLD prior to a fork, we unblock it here. + UnblockSignal(SIGCHLD, fail_fn); + + return pid; +} + static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) { PreApplicationInit(); } @@ -2085,7 +2079,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); if (UNLIKELY(managed_fds_to_close == nullptr)) { - ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector."); + zygote::ZygoteFailure(env, "zygote", nice_name, + "Zygote received a null fds_to_close vector."); } std::vector<int> fds_to_close = @@ -2111,7 +2106,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( fds_to_ignore.push_back(gSystemServerSocketFd); } - pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true); + pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, @@ -2146,10 +2141,10 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( fds_to_ignore.push_back(gSystemServerSocketFd); } - pid_t pid = ForkCommon(env, true, - fds_to_close, - fds_to_ignore, - true); + pid_t pid = zygote::ForkCommon(env, true, + fds_to_close, + fds_to_ignore, + true); if (pid == 0) { // System server prcoess does not need data isolation so no need to // know pkg_data_info_list. @@ -2189,58 +2184,74 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( * ensuring proper file descriptor hygiene. * * @param env Managed runtime environment - * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by blastlas - * in managed code. + * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by the child + * in managed code. -1 indicates none. * @param write_pipe_fd The write FD for the USAP reporting pipe. Manually closed by the - * zygote in managed code. + * zygote in managed code. -1 indicates none. * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by * the FD hygiene code and automatically "closed" in the new USAP. + * @param args_known Arguments for specialization are available; no need to read from a socket * @param is_priority_fork Controls the nice level assigned to the newly created process - * @return + * @return child pid in the parent, 0 in the child */ NO_PAC_FUNC -static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env, - jclass, - jint read_pipe_fd, - jint write_pipe_fd, - jintArray managed_session_socket_fds, - jboolean is_priority_fork) { - std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), - fds_to_ignore(fds_to_close); - +static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env, + jclass, + jint read_pipe_fd, + jint write_pipe_fd, + jintArray managed_session_socket_fds, + jboolean args_known, + jboolean is_priority_fork) { std::vector<int> session_socket_fds = ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds) .value_or(std::vector<int>()); + return zygote::forkApp(env, read_pipe_fd, write_pipe_fd, session_socket_fds, + args_known == JNI_TRUE, is_priority_fork == JNI_TRUE, true); +} - // The USAP Pool Event FD is created during the initialization of the - // USAP pool and should always be valid here. +NO_PAC_FUNC +int zygote::forkApp(JNIEnv* env, + int read_pipe_fd, + int write_pipe_fd, + const std::vector<int>& session_socket_fds, + bool args_known, + bool is_priority_fork, + bool purge) { + + std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), + fds_to_ignore(fds_to_close); fds_to_close.push_back(gZygoteSocketFD); - fds_to_close.push_back(gUsapPoolEventFD); - fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); if (gSystemServerSocketFd != -1) { fds_to_close.push_back(gSystemServerSocketFd); } + if (args_known) { + fds_to_close.push_back(gUsapPoolSocketFD); + } + fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); - fds_to_ignore.push_back(gZygoteSocketFD); fds_to_ignore.push_back(gUsapPoolSocketFD); - fds_to_ignore.push_back(gUsapPoolEventFD); - fds_to_ignore.push_back(read_pipe_fd); - fds_to_ignore.push_back(write_pipe_fd); + fds_to_ignore.push_back(gZygoteSocketFD); + if (read_pipe_fd != -1) { + fds_to_ignore.push_back(read_pipe_fd); + } + if (write_pipe_fd != -1) { + fds_to_ignore.push_back(write_pipe_fd); + } fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end()); + + if (gUsapPoolEventFD != -1) { + fds_to_close.push_back(gUsapPoolEventFD); + fds_to_ignore.push_back(gUsapPoolEventFD); + } if (gSystemServerSocketFd != -1) { + if (args_known) { + fds_to_close.push_back(gSystemServerSocketFd); + } fds_to_ignore.push_back(gSystemServerSocketFd); } - - pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore, - is_priority_fork == JNI_TRUE); - - if (usap_pid != 0) { - ++gUsapPoolCount; - AddUsapTableEntry(usap_pid, read_pipe_fd); - } - - return usap_pid; + return zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, + fds_to_ignore, is_priority_fork == JNI_TRUE, purge); } static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( @@ -2354,7 +2365,7 @@ static void com_android_internal_os_Zygote_nativeInitNativeState(JNIEnv* env, jc */ if (!SetTaskProfiles(0, {})) { - ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed"); + zygote::ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed"); } } @@ -2372,15 +2383,21 @@ static jintArray com_android_internal_os_Zygote_nativeGetUsapPipeFDs(JNIEnv* env return managed_usap_fds; } +/* + * Add the given pid and file descriptor to the Usap table. CriticalNative method. + */ +static void com_android_internal_os_Zygote_nativeAddUsapTableEntry(jint pid, jint read_pipe_fd) { + AddUsapTableEntry(pid, read_pipe_fd); +} + /** - * A JNI wrapper around RemoveUsapTableEntry. + * A JNI wrapper around RemoveUsapTableEntry. CriticalNative method. * * @param env Managed runtime environment * @param usap_pid Process ID of the USAP entry to invalidate * @return True if an entry was invalidated; false otherwise. */ -static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(JNIEnv* env, jclass, - jint usap_pid) { +static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(jint usap_pid) { return RemoveUsapTableEntry(usap_pid); } @@ -2395,7 +2412,8 @@ static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(JNIEnv static jint com_android_internal_os_Zygote_nativeGetUsapPoolEventFD(JNIEnv* env, jclass) { if (gUsapPoolEventFD == -1) { if ((gUsapPoolEventFD = eventfd(0, 0)) == -1) { - ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno))); + zygote::ZygoteFailure(env, "zygote", nullptr, + StringPrintf("Unable to create eventfd: %s", strerror(errno))); } } @@ -2438,12 +2456,12 @@ static void com_android_internal_os_Zygote_nativeEmptyUsapPool(JNIEnv* env, jcla } static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) { - auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1); + auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1); BlockSignal(SIGTERM, fail_fn); } static void com_android_internal_os_Zygote_nativeUnblockSigTerm(JNIEnv* env, jclass) { - auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1); + auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1); UnblockSignal(SIGTERM, fail_fn); } @@ -2549,7 +2567,10 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativePreApplicationInit}, {"nativeInstallSeccompUidGidFilter", "(II)V", (void*)com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter}, - {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap}, + {"nativeForkApp", "(II[IZZ)I", (void*)com_android_internal_os_Zygote_nativeForkApp}, + // @CriticalNative + {"nativeAddUsapTableEntry", "(II)V", + (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry}, {"nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/" "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", @@ -2558,6 +2579,10 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativeInitNativeState}, {"nativeGetUsapPipeFDs", "()[I", (void*)com_android_internal_os_Zygote_nativeGetUsapPipeFDs}, + // @CriticalNative + {"nativeAddUsapTableEntry", "(II)V", + (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry}, + // @CriticalNative {"nativeRemoveUsapTableEntry", "(I)Z", (void*)com_android_internal_os_Zygote_nativeRemoveUsapTableEntry}, {"nativeGetUsapPoolEventFD", "()I", diff --git a/core/jni/com_android_internal_os_Zygote.h b/core/jni/com_android_internal_os_Zygote.h new file mode 100644 index 000000000000..d2da91476bc7 --- /dev/null +++ b/core/jni/com_android_internal_os_Zygote.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _COM_ANDROID_INTERNAL_OS_ZYGOTE_H +#define _COM_ANDROID_INTERNAL_OS_ZYGOTE_H + +#define LOG_TAG "Zygote" +#define ATRACE_TAG ATRACE_TAG_DALVIK + +/* Functions in the callchain during the fork shall not be protected with + Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */ +#ifdef __ARM_FEATURE_PAC_DEFAULT +#ifdef __ARM_FEATURE_BTI_DEFAULT +#define NO_PAC_FUNC __attribute__((target("branch-protection=bti"))) +#else +#define NO_PAC_FUNC __attribute__((target("branch-protection=none"))) +#endif /* __ARM_FEATURE_BTI_DEFAULT */ +#else /* !__ARM_FEATURE_PAC_DEFAULT */ +#define NO_PAC_FUNC +#endif /* __ARM_FEATURE_PAC_DEFAULT */ + +#include <jni.h> +#include <vector> +#include <android-base/stringprintf.h> + +#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ + append(StringPrintf(__VA_ARGS__)) + +namespace android { +namespace zygote { + +NO_PAC_FUNC +pid_t ForkCommon(JNIEnv* env,bool is_system_server, + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore, + bool is_priority_fork, + bool purge = true); + +/** + * Fork a process. The pipe fds are used for usap communication, or -1 in + * other cases. Session_socket_fds are FDs used for zygote communication that must be dealt + * with hygienically, but are not otherwise used here. Args_known indicates that the process + * will be immediately specialized with arguments that are already known, so no usap + * communication is required. Is_priority_fork should be true if this is on the app startup + * critical path. Purge specifies that unused pages should be purged before the fork. + */ +NO_PAC_FUNC +int forkApp(JNIEnv* env, + int read_pipe_fd, + int write_pipe_fd, + const std::vector<int>& session_socket_fds, + bool args_known, + bool is_priority_fork, + bool purge); + +[[noreturn]] +void ZygoteFailure(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + const std::string& msg); + +} // namespace zygote +} // namespace android + +#endif // _COM_ANDROID_INTERNAL_OS_ZYGOTE_ diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp new file mode 100644 index 000000000000..011e8f8f1b8c --- /dev/null +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -0,0 +1,512 @@ +/* + * 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. + */ + +#include "com_android_internal_os_Zygote.h" + +#include <algorithm> +#include <android-base/logging.h> +#include <async_safe/log.h> +#include <cctype> +#include <chrono> +#include <core_jni_helpers.h> +#include <errno.h> +#include <fcntl.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <optional> +#include <poll.h> +#include <unistd.h> +#include <utility> +#include <utils/misc.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <vector> + +namespace android { + +using namespace std::placeholders; +using android::base::StringPrintf; +using android::zygote::ZygoteFailure; + +// WARNING: Knows a little about the wire protocol used to communicate with Zygote. +// TODO: Fix error handling. + +constexpr size_t MAX_COMMAND_BYTES = 12200; +constexpr size_t NICE_NAME_BYTES = 50; + +// A buffer optionally bundled with a file descriptor from which we can fill it. +// Does not own the file descriptor; destroying a NativeCommandBuffer does not +// close the descriptor. +class NativeCommandBuffer { + public: + NativeCommandBuffer(int sourceFd): mEnd(0), mNext(0), mLinesLeft(0), mFd(sourceFd) {} + + // Read mNext line from mFd, filling mBuffer from file descriptor, as needed. + // Return a pair of pointers pointing to the first character, and one past the + // mEnd of the line, i.e. at the newline. Returns nothing on failure. + template<class FailFn> + std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) { + char* result = mBuffer + mNext; + while (true) { + if (mNext == mEnd) { + if (mEnd == MAX_COMMAND_BYTES) { + return {}; + } + if (mFd == -1) { + fail_fn("ZygoteCommandBuffer.readLine attempted to read from mFd -1"); + } + ssize_t nread = TEMP_FAILURE_RETRY(read(mFd, mBuffer + mEnd, MAX_COMMAND_BYTES - mEnd)); + if (nread <= 0) { + if (nread == 0) { + return {}; + } + fail_fn(CREATE_ERROR("session socket read failed: %s", strerror(errno))); + } else if (nread == MAX_COMMAND_BYTES - mEnd) { + // This is pessimistic by one character, but close enough. + fail_fn("ZygoteCommandBuffer overflowed: command too long"); + } + mEnd += nread; + } + // UTF-8 does not allow newline to occur as part of a multibyte character. + char* nl = static_cast<char *>(memchr(mBuffer + mNext, '\n', mEnd - mNext)); + if (nl == nullptr) { + mNext = mEnd; + } else { + mNext = nl - mBuffer + 1; + if (--mLinesLeft < 0) { + fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command"); + } + return std::make_pair(result, nl); + } + } + } + + void reset() { + mNext = 0; + } + + // Make sure the current command is fully buffered, without reading past the current command. + template<class FailFn> + void readAllLines(FailFn fail_fn) { + while (mLinesLeft > 0) { + readLine(fail_fn); + } + } + + void clear() { + // Don't bother to actually clear the buffer; it'll be unmapped in the child anyway. + reset(); + mNiceName[0] = '\0'; + mEnd = 0; + } + + // Insert line into the mBuffer. Checks that the mBuffer is not associated with an mFd. + // Implicitly adds newline separators. Allows mBuffer contents to be explicitly set. + void insert(const char* line, size_t lineLen) { + DCHECK(mFd == -1); + CHECK(mEnd + lineLen < MAX_COMMAND_BYTES); + strncpy(mBuffer + mEnd, line, lineLen); + mBuffer[mEnd + lineLen] = '\n'; + mEnd += lineLen + 1; + } + + // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer + // positioned at the beginning of first argument. Return 0 on EOF. + template<class FailFn> + int getCount(FailFn fail_fn) { + mLinesLeft = 1; + auto line = readLine(fail_fn); + if (!line.has_value()) { + return 0; + } + char* countString = line.value().first; // Newline terminated. + long nArgs = atol(countString); + if (nArgs <= 0 || nArgs >= MAX_COMMAND_BYTES / 2) { + fail_fn(CREATE_ERROR("Unreasonable argument count %ld", nArgs)); + } + mLinesLeft = nArgs; + return static_cast<int>(nArgs); + } + + // Is the mBuffer a simple fork command? + // We disallow request to wrap the child process, child zygotes, anything that + // mentions capabilities or requests uid < minUid. + // We insist that --setuid and --setgid arguments are explicitly included and that the + // command starts with --runtime-args. + // Assumes we are positioned at the beginning of the command after the argument count, + // and leaves the position at some indeterminate position in the buffer. + // As a side effect, this sets mNiceName to a non-empty string, if possible. + template<class FailFn> + bool isSimpleForkCommand(int minUid, FailFn fail_fn) { + if (mLinesLeft <= 0 || mLinesLeft >= MAX_COMMAND_BYTES / 2) { + return false; + } + static const char* RUNTIME_ARGS = "--runtime-args"; + static const char* INVOKE_WITH = "--invoke-with"; + static const char* CHILD_ZYGOTE = "--start-child-zygote"; + static const char* SETUID = "--setuid="; + static const char* SETGID = "--setgid="; + static const char* CAPABILITIES = "--capabilities"; + static const char* NICE_NAME = "--nice-name="; + static const size_t RA_LENGTH = strlen(RUNTIME_ARGS); + static const size_t IW_LENGTH = strlen(INVOKE_WITH); + static const size_t CZ_LENGTH = strlen(CHILD_ZYGOTE); + static const size_t SU_LENGTH = strlen(SETUID); + static const size_t SG_LENGTH = strlen(SETGID); + static const size_t CA_LENGTH = strlen(CAPABILITIES); + static const size_t NN_LENGTH = strlen(NICE_NAME); + + bool saw_setuid = false, saw_setgid = false; + bool saw_runtime_args = false; + + while (mLinesLeft > 0) { + auto read_result = readLine(fail_fn); + if (!read_result.has_value()) { + return false; + } + auto [arg_start, arg_end] = read_result.value(); + if (arg_end - arg_start == RA_LENGTH + && strncmp(arg_start, RUNTIME_ARGS, RA_LENGTH) == 0) { + saw_runtime_args = true; + continue; + } + if (arg_end - arg_start >= NN_LENGTH + && strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) { + size_t name_len = arg_end - (arg_start + NN_LENGTH); + size_t copy_len = std::min(name_len, NICE_NAME_BYTES - 1); + memcpy(mNiceName, arg_start + NN_LENGTH, copy_len); + mNiceName[copy_len] = '\0'; + continue; + } + if (arg_end - arg_start == IW_LENGTH + && strncmp(arg_start, INVOKE_WITH, IW_LENGTH) == 0) { + // This also removes the need for invoke-with security checks here. + return false; + } + if (arg_end - arg_start == CZ_LENGTH + && strncmp(arg_start, CHILD_ZYGOTE, CZ_LENGTH) == 0) { + return false; + } + if (arg_end - arg_start >= CA_LENGTH + && strncmp(arg_start, CAPABILITIES, CA_LENGTH) == 0) { + return false; + } + if (arg_end - arg_start >= SU_LENGTH + && strncmp(arg_start, SETUID, SU_LENGTH) == 0) { + int uid = digitsVal(arg_start + SU_LENGTH, arg_end); + if (uid < minUid) { + return false; + } + saw_setuid = true; + continue; + } + if (arg_end - arg_start >= SG_LENGTH + && strncmp(arg_start, SETGID, SG_LENGTH) == 0) { + int gid = digitsVal(arg_start + SG_LENGTH, arg_end); + if (gid == -1) { + return false; + } + saw_setgid = true; + } + } + return saw_runtime_args && saw_setuid && saw_setgid; + } + + void setFd(int new_fd) { + mFd = new_fd; + } + + int getFd() const { + return mFd; + } + + const char* niceNameAddr() const { + return mNiceName; + } + + // Debug only: + void logState() const { + ALOGD("mbuffer starts with %c%c, nice name is %s, " + "mEnd = %u, mNext = %u, mLinesLeft = %d, mFd = %d", + mBuffer[0], (mBuffer[1] == '\n' ? ' ' : mBuffer[1]), + niceNameAddr(), + static_cast<unsigned>(mEnd), static_cast<unsigned>(mNext), + static_cast<int>(mLinesLeft), mFd); + } + + private: + // Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure. + static int digitsVal(char* start, char* end) { + int result = 0; + if (end - start > 6) { + return -1; + } + for (char* dp = start; dp < end; ++dp) { + if (*dp < '0' || *dp > '9') { + ALOGW("Argument failed integer format check"); + return -1; + } + result = 10 * result + (*dp - '0'); + } + return result; + } + + uint32_t mEnd; // Index of first empty byte in the mBuffer. + uint32_t mNext; // Index of first character past last line returned by readLine. + int32_t mLinesLeft; // Lines in current command that haven't yet been read. + int mFd; // Open file descriptor from which we can read more. -1 if none. + char mNiceName[NICE_NAME_BYTES]; + char mBuffer[MAX_COMMAND_BYTES]; +}; + +static_assert(sizeof(NativeCommandBuffer) < 3 * 4096); + +static int buffersAllocd(0); + +// Get a new NativeCommandBuffer. Can only be called once between freeNativeBuffer calls, +// so that only one buffer exists at a time. +jlong com_android_internal_os_ZygoteCommandBuffer_getNativeBuffer(JNIEnv* env, jclass, jint fd) { + CHECK(buffersAllocd == 0); + ++buffersAllocd; + // MMap explicitly to get it page aligned. + void *bufferMem = mmap(NULL, sizeof(NativeCommandBuffer), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0); + // Currently we mmap and unmap one for every request handled by the Java code. + // That could be improved, but unclear it matters. + if (bufferMem == MAP_FAILED) { + ZygoteFailure(env, nullptr, nullptr, "Failed to map argument buffer"); + } + return (jlong) new(bufferMem) NativeCommandBuffer(fd); +} + +// Delete native command buffer. +void com_android_internal_os_ZygoteCommandBuffer_freeNativeBuffer(JNIEnv* env, jclass, + jlong j_buffer) { + CHECK(buffersAllocd == 1); + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + n_buffer->~NativeCommandBuffer(); + if (munmap(n_buffer, sizeof(NativeCommandBuffer)) != 0) { + ZygoteFailure(env, nullptr, nullptr, "Failed to unmap argument buffer"); + } + --buffersAllocd; +} + +// Clear the buffer, read the line containing the count, and return the count. +jint com_android_internal_os_ZygoteCommandBuffer_nativeGetCount(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, nullptr, nullptr, _1); + return n_buffer->getCount(fail_fn); +} + +// Explicitly insert a string as the last line (argument) of the buffer. +void com_android_internal_os_ZygoteCommandBuffer_insert(JNIEnv* env, jclass, jlong j_buffer, + jstring line) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + size_t lineLen = static_cast<size_t>(env->GetStringUTFLength(line)); + const char* cstring = env->GetStringUTFChars(line, NULL); + n_buffer->insert(cstring, lineLen); + env->ReleaseStringUTFChars(line, cstring); +} + +// Read a line from the buffer, refilling as necessary. +jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1); + auto line = n_buffer->readLine(fail_fn); + if (!line.has_value()) { + fail_fn("Incomplete zygote command"); + } + auto [cresult, endp] = line.value(); + // OK to temporarily clobber the buffer, since this is not thread safe, and we're modifying + // the buffer anyway. + *endp = '\0'; + jstring result = env->NewStringUTF(cresult); + *endp = '\n'; + return result; +} + +// Read all lines from the current command into the buffer, and then reset the buffer, so +// we will start reading again at the beginning of the command, starting with the argument +// count. And we don't need access to the fd to do so. +void com_android_internal_os_ZygoteCommandBuffer_nativeReadFullyAndReset(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1); + n_buffer->readAllLines(fail_fn); + n_buffer->reset(); +} + +// Fork a child as specified by the current command buffer, and refill the command +// buffer from the given socket. So long as the result is another simple fork command, +// repeat this process. +// It must contain a fork command, which is currently restricted not to fork another +// zygote or involve a wrapper process. +// The initial buffer should be partially or entirely read; we read it fully and reset it. +// When we return, the buffer contains the command we couldn't handle, and has been reset(). +// We return false in the parent when we see a command we didn't understand, and thus the +// command in the buffer still needs to be executed. +// We return true in each child. +// We only process fork commands if the peer uid matches expected_uid. +// For every fork command after the first, we check that the requested uid is at +// least minUid. +NO_PAC_FUNC +jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( + JNIEnv* env, + jclass, + jlong j_buffer, + jint zygote_socket_fd, + jint expected_uid, + jint minUid, + jstring managed_nice_name) { + + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + int session_socket = n_buffer->getFd(); + std::vector<int> session_socket_fds {session_socket}; + auto fail_fn_1 = std::bind(ZygoteFailure, env, static_cast<const char*>(nullptr), + static_cast<jstring>(managed_nice_name), _1); + // This binds to the nice name address; the actual names are updated by isSimpleForkCommand: + auto fail_fn_n = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), + static_cast<jstring>(nullptr), _1); + auto fail_fn_z = std::bind(ZygoteFailure, env, "zygote", nullptr, _1); + + struct pollfd fd_structs[2]; + static const int ZYGOTE_IDX = 0; + static const int SESSION_IDX = 1; + fd_structs[ZYGOTE_IDX].fd = zygote_socket_fd; + fd_structs[ZYGOTE_IDX].events = POLLIN; + fd_structs[SESSION_IDX].fd = session_socket; + fd_structs[SESSION_IDX].events = POLLIN; + + struct timeval timeout; + socklen_t timeout_size = sizeof timeout; + if (getsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, &timeout_size) != 0) { + fail_fn_z("Failed to retrieve session socket timeout"); + } + + struct ucred credentials; + socklen_t cred_size = sizeof credentials; + if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 + || cred_size != sizeof credentials) { + fail_fn_1("ForkMany failed to get initial credentials, %s", strerror(errno)); + } + + bool first_time = true; + do { + if (credentials.uid != expected_uid) { + return JNI_FALSE; + } + n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n); + n_buffer->reset(); + int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds, + /*args_known=*/ true, /*is_priority_fork=*/ true, + /*purge=*/ first_time); + if (pid == 0) { + return JNI_TRUE; + } + // We're in the parent. Write big-endian pid, followed by a boolean. + char pid_buf[5]; + int tmp_pid = pid; + for (int i = 3; i >= 0; --i) { + pid_buf[i] = tmp_pid & 0xff; + tmp_pid >>= 8; + } + pid_buf[4] = 0; // Process is not wrapped. + int res = write(session_socket, pid_buf, 5); + if (res != 5) { + if (res == -1) { + (first_time ? fail_fn_1 : fail_fn_n) + (CREATE_ERROR("Pid write error %d: %s", errno, strerror(errno))); + } else { + (first_time ? fail_fn_1 : fail_fn_n) + (CREATE_ERROR("Write unexpectedly returned short: %d < 5", res)); + } + } + // Clear buffer and get count from next command. + n_buffer->clear(); + for (;;) { + // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. + int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */)); + if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { + if (n_buffer->getCount(fail_fn_z) != 0) { + break; + } // else disconnected; + } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) { + fail_fn_z( + CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res)); + } + // We've now seen either a disconnect or connect request. + close(session_socket); + int new_fd = accept(zygote_socket_fd, nullptr, nullptr); + if (new_fd == -1) { + fail_fn_z("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)); + } + if (new_fd != session_socket) { + // Move new_fd back to the old value, so that we don't have to change Java-level data + // structures to reflect a change. This implicitly closes the old one. + if (dup2(new_fd, session_socket) != session_socket) { + fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", + new_fd, session_socket, strerror(errno))); + } + close(new_fd); + } + // If we ever return, we effectively reuse the old Java ZygoteConnection. + // None of its state needs to change. + if (setsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, timeout_size) != 0) { + fail_fn_z(CREATE_ERROR("Failed to set receive timeout for socket %d: %s", + session_socket, strerror(errno))); + } + if (setsockopt(session_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, timeout_size) != 0) { + fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s", + session_socket, strerror(errno))); + } + if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) { + fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno))); + } + if (cred_size != sizeof credentials) { + fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d", + cred_size, static_cast<int>(sizeof credentials))); + } + } + first_time = false; + } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n)); + ALOGW("forkRepeatedly terminated due to non-simple command"); + n_buffer->logState(); + n_buffer->reset(); + return JNI_FALSE; +} + +#define METHOD_NAME(m) com_android_internal_os_ZygoteCommandBuffer_ ## m + +static const JNINativeMethod gMethods[] = { + {"getNativeBuffer", "(I)J", (void *) METHOD_NAME(getNativeBuffer)}, + {"freeNativeBuffer", "(J)V", (void *) METHOD_NAME(freeNativeBuffer)}, + {"insert", "(JLjava/lang/String;)V", (void *) METHOD_NAME(insert)}, + {"nativeNextArg", "(J)Ljava/lang/String;", (void *) METHOD_NAME(nativeNextArg)}, + {"nativeReadFullyAndReset", "(J)V", (void *) METHOD_NAME(nativeReadFullyAndReset)}, + {"nativeGetCount", "(J)I", (void *) METHOD_NAME(nativeGetCount)}, + {"nativeForkRepeatedly", "(JIIILjava/lang/String;)Z", + (void *) METHOD_NAME(nativeForkRepeatedly)}, +}; + +int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv* env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/ZygoteCommandBuffer", gMethods, + NELEM(gMethods)); +} + +} // namespace android diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index d3c2d31779db..ec41a47a8798 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -627,6 +627,7 @@ message ActivityManagerServiceDumpProcessesProto { optional int64 duration_ms = 2; optional string tag = 3; optional int32 type = 4; + optional int32 reason_code = 5; } repeated PendingTempWhitelist pending_temp_whitelist = 26; diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto index 4b1ee02a6c30..f64e146f87b7 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -144,7 +144,7 @@ message StateResidencyProto { */ optional int64 total_state_entry_count = 3; /** - * Last time this state was entered. Time in milliseconds since boot + * Last time this state was entered. Walltime in milliseconds since Unix epoch. */ optional int64 last_entry_timestamp_ms = 4; } @@ -208,7 +208,7 @@ message EnergyConsumerResultProto { /** Unique index identifying the energy consumer. */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ @@ -247,7 +247,7 @@ message EnergyMeasurementProto { */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index a1be865c0c16..ec502c3c272f 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -174,10 +174,10 @@ message DisplayContentProto { // Use root_display_area instead optional WindowContainerProto window_container = 1 [deprecated=true]; optional int32 id = 2; - reserved 3; // stacks - optional DockedStackDividerControllerProto docked_stack_divider_controller = 4 [deprecated=true]; + reserved 3; // RootTasks + optional DockedTaskDividerControllerProto docked_task_divider_controller = 4 [deprecated=true]; // Will be removed soon. - optional PinnedStackControllerProto pinned_stack_controller = 5 [deprecated=true]; + optional PinnedTaskControllerProto pinned_task_controller = 5 [deprecated=true]; /* non app windows */ repeated WindowTokenProto above_app_windows = 6 [deprecated=true]; repeated WindowTokenProto below_app_windows = 7 [deprecated=true]; @@ -256,15 +256,15 @@ message DisplayRotationProto { optional int32 last_orientation = 5 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"]; } -/* represents DockedStackDividerController */ -message DockedStackDividerControllerProto { +/* represents DockedTaskDividerController */ +message DockedTaskDividerControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional bool minimized_dock = 1 [deprecated=true]; } -/* represents PinnedStackController */ -message PinnedStackControllerProto { +/* represents PinnedTaskController */ +message PinnedTaskControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional .android.graphics.RectProto default_bounds = 1 [deprecated=true]; diff --git a/core/res/Android.bp b/core/res/Android.bp index 4cf9cfb7120a..b988b5a92af7 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -44,11 +44,73 @@ license { ], } +genrule { + name: "remote-color-resources-compile-public", + tools: ["aapt2"], + srcs: [ + "remote_color_resources_res/values/public.xml", + ], + out: ["values_public.arsc.flat"], + cmd: "$(location aapt2) compile $(in) -o $(genDir)", +} + +genrule { + name: "remote-color-resources-compile-colors", + tools: ["aapt2"], + srcs: [ + "remote_color_resources_res/values/colors.xml", + ], + out: ["values_colors.arsc.flat"], + cmd: "$(location aapt2) compile $(in) -o $(genDir)", +} + +genrule { + name: "remote-color-resources-apk", + tools: ["aapt2"], + // The first input file in the list must be the manifest + srcs: [ + "RemoteThemeColorsAndroidManifest.xml", + ":remote-color-resources-compile-public", + ":remote-color-resources-compile-colors", + ], + out: ["remote-color-resources.apk"], + cmd: "$(location aapt2) link -o $(out) --manifest $(in)" +} + +genrule { + name: "remote-color-resources-arsc", + srcs: [":remote-color-resources-apk"], + out: ["res/raw/remote_views_color_resources.arsc"], + cmd: "mkdir -p $(genDir)/remote-color-resources-arsc && " + + "unzip -x $(in) resources.arsc -d $(genDir)/remote-color-resources-arsc && " + + "mkdir -p $$(dirname $(out)) && " + + "mv $(genDir)/remote-color-resources-arsc/resources.arsc $(out) && " + + "echo 'Created $(out)'" +} + +genrule { + name: "remote-color-resources-arsc-zip", + tools: ["soong_zip"], + srcs: [ + ":remote-color-resources-arsc", + "remote_color_resources_res/symbols.xml", + ], + out: ["remote_views_color_resources.zip"], + cmd: "INPUTS=($(in)) && " + + "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && " + + "mkdir -p $$RES_DIR/values && " + + "cp $${INPUTS[1]} $$RES_DIR/values && " + + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR && " + + "cp $(out) ." +} + android_app { name: "framework-res", sdk_version: "core_platform", certificate: "platform", + srcs: [":remote-color-resources-arsc"], + // Disable dexpreopt and verify_uses_libraries check as the app // contains no Java code to be dexpreopted. enforce_uses_libs: false, @@ -72,6 +134,10 @@ android_app { "--auto-add-overlay", ], + resource_zips: [ + ":remote-color-resources-arsc-zip", + ], + // Create package-export.apk, which other packages can use to get // PRODUCT-agnostic resource data like IDs and type definitions. export_package_resources: true, @@ -96,6 +162,7 @@ filegroup { srcs: [ "assets/**/*", "res/**/*", + ":remote-color-resources-arsc", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5dd85805cfc1..d5f5d28aa7a2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1284,15 +1284,15 @@ android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO" android:protectionLevel="dangerous|instant" /> - <!-- Allows an application to record audio while in the background. - <p>Protection level: dangerous - --> + <!-- @SystemApi @TestApi Allows an application to record audio while in the background. + This permission is not intended to be held by apps. + <p>Protection level: internal + @hide --> <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO" android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_recordBackgroundAudio" android:description="@string/permdesc_recordBackgroundAudio" - android:permissionFlags="hardRestricted|installerExemptIgnored" - android:protectionLevel="dangerous" /> + android:protectionLevel="internal" /> <!-- ====================================================================== --> <!-- Permissions for activity recognition --> @@ -1368,15 +1368,15 @@ android:backgroundPermission="android.permission.BACKGROUND_CAMERA" android:protectionLevel="dangerous|instant" /> - <!-- Required to be able to access the camera device in the background. - <p>Protection level: dangerous - --> + <!-- @SystemApi @TestApi Required to be able to access the camera device in the background. + This permission is not intended to be held by apps. + <p>Protection level: internal + @hide --> <permission android:name="android.permission.BACKGROUND_CAMERA" android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_backgroundCamera" android:description="@string/permdesc_backgroundCamera" - android:permissionFlags="hardRestricted|installerExemptIgnored" - android:protectionLevel="dangerous" /> + android:protectionLevel="internal" /> <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access system only camera devices. @@ -2641,6 +2641,16 @@ android:label="@string/permlab_manageProfileAndDeviceOwners" android:description="@string/permdesc_manageProfileAndDeviceOwners" /> + <!-- @TestApi @hide Allows an application to reset the record of previous system update freeze + periods. --> + <permission android:name="android.permission.CLEAR_FREEZE_PERIOD" + android:protectionLevel="signature" /> + + <!-- @TestApi @hide Allows an application to force available DevicePolicyManager logs to + DPC. --> + <permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" + android:protectionLevel="signature" /> + <!-- Allows an application to get full detailed information about recently running tasks, with full fidelity to the real state. @hide --> @@ -3972,17 +3982,6 @@ <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to use the package installer v2 APIs. - <p>The package installer v2 APIs are still a work in progress and we're - currently validating they work in all scenarios. - <p>Not for use by third-party applications. - TODO(b/152310230): use this permission to protect only Incremental installations - once the APIs are confirmed to be sufficient. - @hide - --> - <permission android:name="com.android.permission.USE_INSTALLER_V2" - android:protectionLevel="signature|verifier" /> - <!-- Allows an application to use System Data Loaders. <p>Not for use by third-party applications. @hide @@ -5421,6 +5420,12 @@ @hide --> <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" android:protectionLevel="signature" /> + + <!-- @SystemApi Allows sensor privacy changes to be observed. + @hide --> + <permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES} intent. @hide --> diff --git a/core/res/RemoteThemeColorsAndroidManifest.xml b/core/res/RemoteThemeColorsAndroidManifest.xml new file mode 100644 index 000000000000..11970fd18979 --- /dev/null +++ b/core/res/RemoteThemeColorsAndroidManifest.xml @@ -0,0 +1,5 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> +<application/> +</manifest> + diff --git a/core/res/remote_color_resources_res/symbols.xml b/core/res/remote_color_resources_res/symbols.xml new file mode 100644 index 000000000000..82d5e3e2da6d --- /dev/null +++ b/core/res/remote_color_resources_res/symbols.xml @@ -0,0 +1,4 @@ +<resources> + <!-- ARSC file used to overlay local colors when rendering a RemoteViews --> + <java-symbol type="raw" name="remote_views_color_resources" /> +</resources> diff --git a/core/res/remote_color_resources_res/values/colors.xml b/core/res/remote_color_resources_res/values/colors.xml new file mode 100644 index 000000000000..295f16e959e6 --- /dev/null +++ b/core/res/remote_color_resources_res/values/colors.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Note: the values of the colors doesn't really matter (they will always be overwritten before used), but they help a lot debugging, to find out which color is where in the ARSC file. --> + <color name="system_primary_0">#01010101</color> + <color name="system_primary_50">#02020202</color> + <color name="system_primary_100">#03030303</color> + <color name="system_primary_200">#04040404</color> + <color name="system_primary_300">#05050505</color> + <color name="system_primary_400">#06060606</color> + <color name="system_primary_500">#07070707</color> + <color name="system_primary_600">#08080808</color> + <color name="system_primary_700">#09090909</color> + <color name="system_primary_800">#0a0a0a0a</color> + <color name="system_primary_900">#0b0b0b0b</color> + <color name="system_primary_1000">#0c0c0c0c</color> + <color name="system_secondary_0">#10101010</color> + <color name="system_secondary_50">#20202020</color> + <color name="system_secondary_100">#30303030</color> + <color name="system_secondary_200">#40404040</color> + <color name="system_secondary_300">#50505050</color> + <color name="system_secondary_400">#60606060</color> + <color name="system_secondary_500">#70707070</color> + <color name="system_secondary_600">#80808080</color> + <color name="system_secondary_700">#90909090</color> + <color name="system_secondary_800">#a0a0a0a0</color> + <color name="system_secondary_900">#b0b0b0b0</color> + <color name="system_secondary_1000">#c0c0c0c0</color> + <color name="system_neutral_0">#1f1f1f1f</color> + <color name="system_neutral_50">#2f2f2f2f</color> + <color name="system_neutral_100">#3f3f3f3f</color> + <color name="system_neutral_200">#4f4f4f4f</color> + <color name="system_neutral_300">#5f5f5f5f</color> + <color name="system_neutral_400">#6f6f6f6f</color> + <color name="system_neutral_500">#7f7f7f7f</color> + <color name="system_neutral_600">#8f8f8f8f</color> + <color name="system_neutral_700">#9f9f9f9f</color> + <color name="system_neutral_800">#afafafaf</color> + <color name="system_neutral_900">#bfbfbfbf</color> + <color name="system_neutral_1000">#cfcfcfcf</color> +</resources> diff --git a/core/res/remote_color_resources_res/values/public.xml b/core/res/remote_color_resources_res/values/public.xml new file mode 100644 index 000000000000..e628f09b8327 --- /dev/null +++ b/core/res/remote_color_resources_res/values/public.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <public-group type="color" first-id="0x0106001d"> + <public name="system_primary_0" /> + <public name="system_primary_50" /> + <public name="system_primary_100" /> + <public name="system_primary_200" /> + <public name="system_primary_300" /> + <public name="system_primary_400" /> + <public name="system_primary_500" /> + <public name="system_primary_600" /> + <public name="system_primary_700" /> + <public name="system_primary_800" /> + <public name="system_primary_900" /> + <public name="system_primary_1000" /> + <public name="system_secondary_0" /> + <public name="system_secondary_50" /> + <public name="system_secondary_100" /> + <public name="system_secondary_200" /> + <public name="system_secondary_300" /> + <public name="system_secondary_400" /> + <public name="system_secondary_500" /> + <public name="system_secondary_600" /> + <public name="system_secondary_700" /> + <public name="system_secondary_800" /> + <public name="system_secondary_900" /> + <public name="system_secondary_1000" /> + <public name="system_neutral_0" /> + <public name="system_neutral_50" /> + <public name="system_neutral_100" /> + <public name="system_neutral_200" /> + <public name="system_neutral_300" /> + <public name="system_neutral_400" /> + <public name="system_neutral_500" /> + <public name="system_neutral_600" /> + <public name="system_neutral_700" /> + <public name="system_neutral_800" /> + <public name="system_neutral_900" /> + <public name="system_neutral_1000" /> + </public-group> +</resources> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 62114630a328..1de1d049197c 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -17,8 +17,8 @@ <NotificationHeaderView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/notification_header" - android:layout_width="wrap_content" - android:layout_height="@dimen/notification_header_solo_height" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_header_height" android:layout_marginBottom="@dimen/notification_header_margin_bottom" android:clipChildren="false" android:gravity="center_vertical" diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml index de537b205866..2d1c3422ca36 100644 --- a/core/res/res/layout/notification_template_material_big_base.xml +++ b/core/res/res/layout/notification_template_material_big_base.xml @@ -38,11 +38,7 @@ android:layout_gravity="top" > - <include - layout="@layout/notification_template_header" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_header_big_height" - /> + <include layout="@layout/notification_template_header" /> <LinearLayout android:id="@+id/notification_main_column" diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml index 696cb6572e29..bdd4430a7985 100644 --- a/core/res/res/layout/notification_template_material_big_media.xml +++ b/core/res/res/layout/notification_template_material_big_media.xml @@ -36,7 +36,6 @@ layout="@layout/notification_template_header" android:layout_width="match_parent" android:layout_height="@dimen/media_notification_header_height" - android:layout_gravity="start" /> <LinearLayout diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml index e1b7bc4d7bca..6f3c77ff72a4 100644 --- a/core/res/res/layout/notification_template_material_big_picture.xml +++ b/core/res/res/layout/notification_template_material_big_picture.xml @@ -23,11 +23,7 @@ android:clipChildren="false" > - <include - layout="@layout/notification_template_header" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_header_big_height" - /> + <include layout="@layout/notification_template_header" /> <include layout="@layout/notification_template_right_icon" /> diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml index 2452a32b21eb..2954ba2a0903 100644 --- a/core/res/res/layout/notification_template_material_big_text.xml +++ b/core/res/res/layout/notification_template_material_big_text.xml @@ -23,11 +23,7 @@ android:tag="bigText" > - <include - layout="@layout/notification_template_header" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_header_big_height" - /> + <include layout="@layout/notification_template_header" /> <com.android.internal.widget.RemeasuringLinearLayout android:id="@+id/notification_action_list_margin_target" diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index 7daccd2b8544..baffdd5ac0f1 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -32,7 +32,8 @@ /> <include layout="@layout/notification_template_header" android:layout_width="match_parent" - android:layout_height="@dimen/media_notification_header_height" /> + android:layout_height="@dimen/media_notification_header_height" + /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml index 513da5e431e5..e6d724f1ecb2 100644 --- a/core/res/res/layout/splash_screen_view.xml +++ b/core/res/res/layout/splash_screen_view.xml @@ -21,14 +21,16 @@ android:orientation="vertical"> <View android:id="@+id/splashscreen_icon_view" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_gravity="center"/> + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:contentDescription="@string/splash_screen_view_icon_description"/> <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:layout_marginBottom="60dp" + android:contentDescription="@string/splash_screen_view_branding_description"/> </android.window.SplashScreenView>
\ No newline at end of file diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index 29f2b6f14b57..4410e944db39 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -26,6 +26,10 @@ <color name="notification_default_color_dark">#ddffffff</color> + <color name="notification_primary_text_color_current">@color/notification_primary_text_color_dark</color> + <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_dark</color> + <color name="notification_default_color_current">@color/notification_default_color_dark</color> + <color name="chooser_row_divider">@color/list_divider_color_dark</color> <color name="chooser_gradient_background">@color/loading_gradient_background_color_dark</color> <color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color> diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml index 952cdd08451c..3fd82b874a84 100644 --- a/core/res/res/values-night/values.xml +++ b/core/res/res/values-night/values.xml @@ -28,9 +28,4 @@ </style> <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Dialog" /> - - <style name="TextAppearance.Material.Notification"> - <item name="textColor">?attr/textColorPrimary</item> - <item name="textSize">@dimen/notification_text_size</item> - </style> </resources>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 99f58ee1f8a6..735e122444ef 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4127,6 +4127,94 @@ user's time zone. Please refer to {@link java.util.TimeZone} for more information about time zone ids. --> <attr name="timeZone" format="string"/> + <!-- Tint to apply to the dial graphic. --> + <attr name="dialTint" format="color" /> + <!-- Blending mode used to apply the dial graphic tint. --> + <attr name="dialTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the hour hand graphic. --> + <attr name="hand_hourTint" format="color" /> + <!-- Blending mode used to apply the hour hand graphic tint. --> + <attr name="hand_hourTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the minute hand graphic. --> + <attr name="hand_minuteTint" format="color" /> + <!-- Blending mode used to apply the minute hand graphic tint. --> + <attr name="hand_minuteTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> + <!-- Tint to apply to the second hand graphic. --> + <attr name="hand_secondTint" format="color" /> + <!-- Blending mode used to apply the second hand graphic tint. --> + <attr name="hand_secondTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> </declare-styleable> <declare-styleable name="Button"> </declare-styleable> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 0ae6a76e2a60..45e11ba9820e 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1893,6 +1893,11 @@ <!-- User data will remain unchanged during rollback. --> <enum name="retain" value="2" /> </attr> + + <!-- Applications can set this attribute to an xml resource within their app where they + specified the rules determining which files and directories can be copied from the device + as part of backup or transfer operations. --> + <attr name="dataExtractionRules" format="reference"/> </declare-styleable> <!-- An attribution is a logical part of an app and is identified by a tag. diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 59c260cecfbf..d79c01faaa36 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -143,6 +143,10 @@ <color name="notification_default_color_dark">@color/primary_text_default_material_light</color> <color name="notification_default_color_light">#a3202124</color> + <color name="notification_primary_text_color_current">@color/notification_primary_text_color_light</color> + <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_light</color> + <color name="notification_default_color_current">@color/notification_default_color_light</color> + <color name="notification_default_color">#757575</color> <!-- Gray 600 --> <color name="notification_action_button_text_color">@color/notification_default_color</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9a917b72a5fd..12cb3980f785 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1173,6 +1173,9 @@ <!-- Default value for LED off time when the battery is low on charge in miliseconds --> <integer name="config_notificationsBatteryLedOff">2875</integer> + <!-- If true, only colorized CallStyle notifications will apply custom colors --> + <bool name="config_callNotificationActionColorsRequireColorized">true</bool> + <!-- Number of notifications to keep in the notification service historical archive --> <integer name="config_notificationServiceArchiveSize">100</integer> @@ -1950,6 +1953,8 @@ <string name="config_systemShell" translatable="false">com.android.shell</string> <!-- The name of the package that will hold the system contacts role. --> <string name="config_systemContacts" translatable="false">com.android.contacts</string> + <!-- The name of the package that will hold the speech recognizer role by default. --> + <string name="config_systemSpeechRecognizer" translatable="false"></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 1ca54985dfbc..695a831faf97 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -271,11 +271,8 @@ <!-- The top margin before the notification progress bar. --> <dimen name="notification_progress_margin_top">8dp</dimen> - <!-- height of the notification header when the notification is alone (minimized / groups) --> - <dimen name="notification_header_solo_height">48dp</dimen> - - <!-- height of the notification header when in a "big" layout --> - <dimen name="notification_header_big_height">56dp</dimen> + <!-- height of the notification header --> + <dimen name="notification_header_height">56dp</dimen> <!-- The height of the background for a notification header on a group --> <dimen name="notification_header_background_height">49.5dp</dimen> @@ -365,7 +362,7 @@ <dimen name="media_notification_expanded_image_margin_bottom">20dp</dimen> <!-- The absolute height for the header in a media notification. --> - <dimen name="media_notification_header_height">@dimen/notification_header_big_height</dimen> + <dimen name="media_notification_header_height">@dimen/notification_header_height</dimen> <!-- The margin of the content to an image--> <dimen name="notification_content_image_margin_end">8dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index a1ea61c447c0..2004d0a8d15c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3076,6 +3076,15 @@ <public name="maxResizeHeight" /> <public name="targetCellWidth" /> <public name="targetCellHeight" /> + <public name="dialTint"/> + <public name="dialTintMode"/> + <public name="hand_hourTint"/> + <public name="hand_hourTintMode"/> + <public name="hand_minuteTint"/> + <public name="hand_minuteTintMode"/> + <public name="hand_secondTint"/> + <public name="hand_secondTintMode"/> + <public name="dataExtractionRules"/> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3157,6 +3166,8 @@ <public name="config_customMediaKeyDispatcher" /> <!-- @hide @SystemApi --> <public name="config_customMediaSessionPolicyProvider" /> + <!-- @hide @SystemApi --> + <public name="config_systemSpeechRecognizer" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3505dee134c0..9eb2f145da38 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1644,7 +1644,7 @@ <!-- Message shown during acqusition when the user's face is turned too far left or right [CHAR LIMIT=50] --> <string name="face_acquired_pan_too_extreme">Turn your head a little less.</string> <!-- Message shown during acqusition when the user's face is tilted too high or too low [CHAR LIMIT=50] --> - <string name="face_acquired_tilt_too_extreme">Turn your head a little less.</string> + <string name="face_acquired_tilt_too_extreme">Tilt your head a little less.</string> <!-- Message shown during acquisiton when the user's face is tilted too far left or right [CHAR LIMIT=50] --> <string name="face_acquired_roll_too_extreme">Turn your head a little less.</string> <!-- Message shown during acquisition when the user's face is obscured [CHAR LIMIT=50] --> @@ -4538,7 +4538,7 @@ <string name="color_correction_feature_name">Color Correction</string> <!-- Title of Reduce Brightness feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] --> - <string name="reduce_bright_colors_feature_name">Reduce Brightness</string> + <string name="reduce_bright_colors_feature_name">Reduce brightness</string> <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string> @@ -5849,4 +5849,8 @@ ul.</string> <!--- Label for notification channel for all sensor privacy related notifications. [CHAR LIMIT=NONE] --> <string name="sensor_privacy_notification_channel_label">Sensor Privacy</string> + <!-- Content description for the icon on the splash screen. [CHAR LIMIT=50] --> + <string name="splash_screen_view_icon_description">Application icon</string> + <!-- Content description for the branding image on the splash screen. [CHAR LIMIT=50] --> + <string name="splash_screen_view_branding_description">Application branding image</string> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 158289a39d5c..3c4a5d4aab73 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -466,18 +466,20 @@ please see styles_device_defaults.xml. </style> <style name="TextAppearance.Material.Notification"> - <item name="textColor">@color/notification_secondary_text_color_light</item> + <item name="textColor">@color/notification_secondary_text_color_current</item> <item name="textSize">@dimen/notification_text_size</item> </style> <style name="TextAppearance.Material.Notification.Reply" /> <style name="TextAppearance.Material.Notification.Title"> + <item name="textColor">@color/notification_primary_text_color_current</item> <item name="fontFamily">sans-serif-medium</item> <item name="textSize">@dimen/notification_title_text_size</item> </style> <style name="TextAppearance.Material.Notification.BigTitle"> + <item name="textColor">@color/notification_primary_text_color_current</item> <item name="fontFamily">sans-serif-medium</item> <item name="textSize">@dimen/notification_big_title_text_size</item> </style> @@ -492,9 +494,8 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Material.Notification.Time" parent="TextAppearance.Material.Notification.Info" /> - <style name="TextAppearance.Material.Notification.Emphasis"> - <item name="textColor">#66000000</item> - </style> + <!-- unused; keep identical to parent --> + <style name="TextAppearance.Material.Notification.Emphasis"/> <style name="TextAppearance.Material.Notification.Conversation.AppName" parent="TextAppearance.Material.Notification.Title"> <item name="android:textSize">16sp</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e73d6e3af3cb..7ad05de1bdd0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2286,6 +2286,7 @@ <java-symbol type="string" name="config_wifi_tether_enable" /> <java-symbol type="bool" name="config_intrusiveNotificationLed" /> <java-symbol type="bool" name="config_notificationBadging" /> + <java-symbol type="bool" name="config_callNotificationActionColorsRequireColorized" /> <java-symbol type="dimen" name="preference_fragment_padding_bottom" /> <java-symbol type="dimen" name="preference_fragment_padding_side" /> <java-symbol type="drawable" name="expander_ic_maximized" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index e7e049da18c9..16d720b891e2 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -238,6 +238,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -271,6 +273,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -306,6 +310,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -340,6 +346,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -417,6 +425,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -449,6 +459,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -482,6 +494,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -531,6 +545,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -565,6 +581,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -597,6 +615,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -631,6 +651,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -664,6 +686,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -697,6 +721,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -730,6 +756,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -763,6 +791,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -800,6 +830,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -834,6 +866,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -865,6 +899,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -1069,6 +1105,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1101,6 +1139,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1134,6 +1174,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1169,6 +1211,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1203,6 +1247,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1281,6 +1327,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1316,6 +1364,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1352,6 +1402,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1426,6 +1478,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1463,6 +1517,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1498,6 +1554,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1532,6 +1590,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1565,6 +1625,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1598,6 +1660,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1629,6 +1693,8 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1750,6 +1816,8 @@ easier. <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_dark</item> <item name="colorError">@color/error_color_device_default_dark</item> + <item name="colorBackground">@color/background_device_default_dark</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> @@ -1782,6 +1850,8 @@ easier. <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1824,6 +1894,8 @@ easier. <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> @@ -1859,6 +1931,8 @@ easier. <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorError">@color/error_color_device_default_light</item> + <item name="colorBackground">@color/background_device_default_light</item> + <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> diff --git a/core/tests/GameManagerTests/Android.bp b/core/tests/GameManagerTests/Android.bp index e1787762e067..2789e9f316d1 100644 --- a/core/tests/GameManagerTests/Android.bp +++ b/core/tests/GameManagerTests/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "FrameworksCoreGameManagerTests", // Include all test java files diff --git a/core/tests/batterystatstests/BatteryStatsLoadTests/Android.bp b/core/tests/batterystatstests/BatteryStatsLoadTests/Android.bp index ade97b81e775..1c0ea839ec02 100644 --- a/core/tests/batterystatstests/BatteryStatsLoadTests/Android.bp +++ b/core/tests/batterystatstests/BatteryStatsLoadTests/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "BatteryStatsLoadTests", srcs: ["src/**/*.java"], diff --git a/core/tests/batterystatstests/BatteryStatsViewer/Android.bp b/core/tests/batterystatstests/BatteryStatsViewer/Android.bp index 1e0498be5800..c2e7d81cb283 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/Android.bp +++ b/core/tests/batterystatstests/BatteryStatsViewer/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "BatteryStatsViewer", srcs: ["src/**/*.java"], diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java new file mode 100644 index 000000000000..1cf430205627 --- /dev/null +++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.SuppressLint; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.MergedConfiguration; +import android.view.InsetsSource; +import android.view.InsetsState; + +import com.google.caliper.AfterExperiment; +import com.google.caliper.BeforeExperiment; + +/** + * Benchmark of read/write large Parcelable class. This also shows the performance of different + * implementations for nested Parcelable class: + * <ul> + * <li>Well-written read/writeFromParcel (direct access)</li> + * <li>read/writeTypedObject (object creation + addition int to indicate nullity)</li> + * <li>read/writeParcelable (object creation + addition type String)</li> + * </ul> + */ +public class ParcelableBenchmark { + private Parcel mParcel; + + @BeforeExperiment + protected void setUp() { + mParcel = Parcel.obtain(); + } + + @AfterExperiment + protected void tearDown() { + mParcel.recycle(); + mParcel = null; + } + + public void timeReadWriteMergedConfiguration(int reps) { + final MergedConfiguration mergedConfiguration = new MergedConfiguration(); + for (int i = 0; i < reps; i++) { + mergedConfiguration.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + mergedConfiguration.readFromParcel(mParcel); + } + } + + public void timeReadWriteInsetsState(int reps) { + final InsetsState insetsState = new InsetsState(); + for (int i = 0; i < InsetsState.SIZE; i++) { + insetsState.addSource(new InsetsSource(i)); + } + for (int i = 0; i < reps; i++) { + insetsState.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + insetsState.readFromParcel(mParcel); + } + } + + public void timeReadWritePointArray(int reps) { + final PointArray pointArray = new PointArray(); + for (int i = 0; i < reps; i++) { + pointArray.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + pointArray.readFromParcel(mParcel); + } + } + + public void timeReadWritePointArrayFast(int reps) { + final PointArrayFast pointArray = new PointArrayFast(); + for (int i = 0; i < reps; i++) { + pointArray.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + pointArray.readFromParcel(mParcel); + } + } + + @SuppressLint("ParcelCreator") + private static class PointArray implements Parcelable { + Rect mBounds = new Rect(); + Point[] mPoints = new Point[10]; + { + for (int i = 0; i < mPoints.length; i++) { + mPoints[i] = new Point(); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mBounds, flags); + dest.writeParcelableArray(mPoints, flags); + } + + void readFromParcel(Parcel in) { + mBounds = in.readParcelable(Rect.class.getClassLoader()); + mPoints = in.readParcelableArray(Point.class.getClassLoader(), Point.class); + } + + @Override + public int describeContents() { + return 0; + } + } + + @SuppressLint("ParcelCreator") + private static class PointArrayFast extends PointArray { + + @Override + public void writeToParcel(Parcel dest, int flags) { + mBounds.writeToParcel(dest, flags); + dest.writeTypedArray(mPoints, flags); + } + + @Override + void readFromParcel(Parcel in) { + mBounds.readFromParcel(in); + in.readTypedArray(mPoints, Point.CREATOR); + } + } +} diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java index 1573c19b89a3..7cb680499d98 100644 --- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java @@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; +import android.os.UserHandle; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -124,13 +125,13 @@ public class PeopleSpaceTileTest { } @Test - public void testUid() { + public void testUserHandle() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) - .setUid(42) + .setUserHandle(new UserHandle(0)) .build(); - assertThat(tile.getUid()).isEqualTo(42); + assertThat(tile.getUserHandle()).isEqualTo(new UserHandle(0)); } @Test @@ -227,7 +228,7 @@ public class PeopleSpaceTileTest { .setUserName("name") .setUserIcon(mIcon) .setContactUri(Uri.parse("contact")) - .setUid(42) + .setUserHandle(new UserHandle(1)) .setPackageName("package.name") .setLastInteractionTimestamp(7L) .setIsImportantConversation(true) @@ -246,7 +247,7 @@ public class PeopleSpaceTileTest { assertThat(readTile.getUserName()).isEqualTo(tile.getUserName()); assertThat(readTile.getUserIcon().toString()).isEqualTo(tile.getUserIcon().toString()); assertThat(readTile.getContactUri()).isEqualTo(tile.getContactUri()); - assertThat(readTile.getUid()).isEqualTo(tile.getUid()); + assertThat(readTile.getUserHandle()).isEqualTo(tile.getUserHandle()); assertThat(readTile.getPackageName()).isEqualTo(tile.getPackageName()); assertThat(readTile.getLastInteractionTimestamp()).isEqualTo( tile.getLastInteractionTimestamp()); diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java new file mode 100644 index 000000000000..5dbe03e8b42b --- /dev/null +++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static android.util.RotationUtils.rotateBounds; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Rect; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link RotationUtils}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:RotationUtilsTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RotationUtilsTest { + + @Test + public void testRotateBounds() { + Rect testParent = new Rect(0, 0, 1000, 600); + Rect testInner = new Rect(40, 20, 120, 80); + + Rect testResult = new Rect(testInner); + rotateBounds(testResult, testParent, ROTATION_90); + assertEquals(new Rect(20, 880, 80, 960), testResult); + + testResult.set(testInner); + rotateBounds(testResult, testParent, ROTATION_180); + assertEquals(new Rect(880, 520, 960, 580), testResult); + + testResult.set(testInner); + rotateBounds(testResult, testParent, ROTATION_270); + assertEquals(new Rect(520, 40, 580, 120), testResult); + } +} diff --git a/core/tests/coretests/src/android/view/BlurAggregatorTest.java b/core/tests/coretests/src/android/view/BlurAggregatorTest.java new file mode 100644 index 000000000000..b01f2755efdd --- /dev/null +++ b/core/tests/coretests/src/android/view/BlurAggregatorTest.java @@ -0,0 +1,318 @@ +/* + * 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.view; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.graphics.drawable.BackgroundBlurDrawable; +import com.android.internal.graphics.drawable.BackgroundBlurDrawable.Aggregator; +import com.android.internal.graphics.drawable.BackgroundBlurDrawable.BlurRegion; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BlurAggregatorTest { + private static final int TEST_BLUR_RADIUS = 30; + private static final int TEST_FRAME_NUMBER = 1; + + private Context mContext; + + private Aggregator mAggregator; + private BackgroundBlurDrawable mDrawable; + + private ViewRootImpl mViewRoot; + + @Before + public void setUp() { + mContext = getInstrumentation().getTargetContext(); + getInstrumentation().runOnMainSync(() -> { + mViewRoot = new ViewRootImpl(mContext, mContext.getDisplayNoVerify()); + }); + mAggregator = new Aggregator(mViewRoot); + mDrawable = createTestBackgroundBlurDrawable(); + } + + private BackgroundBlurDrawable createTestBackgroundBlurDrawable() { + final BackgroundBlurDrawable drawable = mAggregator.createBackgroundBlurDrawable(mContext); + drawable.setBlurRadius(TEST_BLUR_RADIUS); + final boolean hasUpdates = mAggregator.hasUpdates(); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates); + return drawable; + } + + @Test + public void testBlurRadiusUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setBlurRadius(TEST_BLUR_RADIUS); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setBlurRadius(0); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setBlurRadius(TEST_BLUR_RADIUS); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius); + assertFalse(mAggregator.hasUpdates()); + + } + + @Test + public void testAlphaUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setAlpha(20); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setAlpha(20); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setAlpha(0); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testCornerRadiusUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setCornerRadius(1f, 2f, 3f, 4f); + assertTrue(mAggregator.hasUpdates()); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testVisibleUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setVisible(false, /* restart= */false); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setVisible(true, /* restart= */ false); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testBlurRegionCopyForRtIsSameIfNoUiUpdates() { + mDrawable.setBlurRadius(30); + BlurRegion[] blurRegions1 = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions1.length); + assertEquals(30, blurRegions1[0].blurRadius); + + BlurRegion[] blurRegions2 = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(blurRegions1, blurRegions2); + } + + @Test + public void testPositionUpdateAppearsInBlurRegion() { + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, + mAggregator.hasUpdates()); + assertEquals(1, blurRegions[0].rect.left); + assertEquals(2, blurRegions[0].rect.top); + assertEquals(3, blurRegions[0].rect.right); + assertEquals(4, blurRegions[0].rect.bottom); + } + + @Test + public void testNoBlurRegionsDispatchedWhenNoUpdates() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNull(blurRegionsForSf); + } + + @Test + public void testBlurRegionDispatchedIfOnlyDrawableUpdated() { + mDrawable.setBlurRadius(50); + final boolean hasUpdates = mAggregator.hasUpdates(); + assertTrue(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals(50f, blurRegionsForSf[0][0]); + } + + @Test + public void testBlurRegionDispatchedIfOnlyPositionUpdated() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + } + + @Test + public void testPositionUpdateIsAppliedInNextFrameIfMissed() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + } + + @Test + public void testMultipleDrawablesDispatchedToSfIfOneIsUpdated() { + final BackgroundBlurDrawable drawable2 = createTestBackgroundBlurDrawable(); + drawable2.setBlurRadius(50); + final boolean hasUpdates = mAggregator.hasUpdates(); + assertTrue(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(2, blurRegions.length); + + // Check that an update in one of the drawables triggers a dispatch of all blur regions + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(2, blurRegionsForSf.length); + + // Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER + blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false); + assertNull(blurRegionsForSf); + + // Check that a position update triggers a dispatch of all blur regions + drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(2, blurRegionsForSf.length); + } + + @Test + public void testUiThreadUpdatesDoNotChangeStateOnRenderThread() { + // Updates for frame N + mDrawable.setBlurRadius(50); + mDrawable.setCornerRadius(1, 2, 3, 4); + mDrawable.setAlpha(20); + + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(50, blurRegions[0].blurRadius); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + + // Updates for frame N+1 + mDrawable.setBlurRadius(60); + mDrawable.setCornerRadius(10, 20, 30, 40); + mDrawable.setAlpha(40); + + // Assert state for frame N is untouched + assertEquals(50, blurRegions[0].blurRadius); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + } + + @Test + public void testPositionUpdatesForFutureFramesAreNotAppliedForCurrentFrame() { + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8); + + final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + // Assert state for first frame is not affected by update for second frame + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + + final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false); + assertNotNull(blurRegionsForSfForNextFrame); + assertEquals(1, blurRegionsForSfForNextFrame.length); + // Assert second frame updates are applied normally + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSfForNextFrame[0][0]); + assertEquals(5f, blurRegionsForSfForNextFrame[0][2]); + assertEquals(6f, blurRegionsForSfForNextFrame[0][3]); + assertEquals(7f, blurRegionsForSfForNextFrame[0][4]); + assertEquals(8f, blurRegionsForSfForNextFrame[0][5]); + } + +} diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS index 5031ff913e6d..fa1aa5eab26c 100644 --- a/core/tests/coretests/src/android/view/OWNERS +++ b/core/tests/coretests/src/android/view/OWNERS @@ -1,7 +1,7 @@ # Input -per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com -per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com -per-file VelocityTest.java = michaelwr@google.com, svv@google.com +per-file *MotionEventTest.* = file:/services/core/java/com/android/server/input/OWNERS +per-file *KeyEventTest.* = file:/services/core/java/com/android/server/input/OWNERS +per-file VelocityTest.java = file:/services/core/java/com/android/server/input/OWNERS # WindowManager per-file *Display* = file:/services/core/java/com/android/server/wm/OWNERS @@ -9,3 +9,6 @@ per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index b9cf1e4a234c..516fb76eeaf7 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -16,16 +16,12 @@ package android.view; -import static androidx.test.InstrumentationRegistry.getInstrumentation; import static androidx.test.InstrumentationRegistry.getTargetContext; 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.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -35,6 +31,7 @@ import static org.mockito.Mockito.when; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; +import android.os.ICancellationSignal; import androidx.test.runner.AndroidJUnit4; @@ -42,9 +39,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; /** * Tests of {@link ScrollCaptureConnection}. @@ -56,261 +51,138 @@ public class ScrollCaptureConnectionTest { private final Point mPositionInWindow = new Point(1, 2); private final Rect mLocalVisibleRect = new Rect(2, 3, 4, 5); private final Rect mScrollBounds = new Rect(3, 4, 5, 6); + private final TestScrollCaptureCallback mCallback = new TestScrollCaptureCallback(); + + private ScrollCaptureTarget mTarget; + private ScrollCaptureConnection mConnection; private Handler mHandler; - private ScrollCaptureTarget mTarget1; @Mock private Surface mSurface; @Mock - private IScrollCaptureCallbacks mConnectionCallbacks; - @Mock - private View mMockView1; + private IScrollCaptureCallbacks mRemote; @Mock - private ScrollCaptureCallback mCallback1; + private View mView; @Before public void setUp() { MockitoAnnotations.initMocks(this); mHandler = new Handler(getTargetContext().getMainLooper()); + when(mSurface.isValid()).thenReturn(true); + when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE); - when(mMockView1.getHandler()).thenReturn(mHandler); - when(mMockView1.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE); - - mTarget1 = new ScrollCaptureTarget( - mMockView1, mLocalVisibleRect, mPositionInWindow, mCallback1); - mTarget1.setScrollBounds(mScrollBounds); - } - - /** Test the DelayedAction timeout helper class works as expected. */ - @Test - public void testDelayedAction() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(200); - } catch (InterruptedException ex) { - /* ignore */ - } - getInstrumentation().waitForIdleSync(); - assertFalse(delayed.cancel()); - assertFalse(delayed.timeoutNow()); - verify(action, times(1)).run(); - } - - /** Test the DelayedAction cancel() */ - @Test - public void testDelayedAction_cancel() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(50); - } catch (InterruptedException ex) { - /* ignore */ - } - assertTrue(delayed.cancel()); - assertFalse(delayed.timeoutNow()); - try { - Thread.sleep(200); - } catch (InterruptedException ex) { - /* ignore */ - } - getInstrumentation().waitForIdleSync(); - verify(action, never()).run(); - } - - /** Test the DelayedAction timeoutNow() - for testing only */ - @Test - public void testDelayedAction_timeoutNow() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(50); - } catch (InterruptedException ex) { - /* ignore */ - } - assertTrue(delayed.timeoutNow()); - assertFalse(delayed.cancel()); - getInstrumentation().waitForIdleSync(); - verify(action, times(1)).run(); + mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback); + mTarget.setScrollBounds(mScrollBounds); + mConnection = new ScrollCaptureConnection(Runnable::run, mTarget, mRemote); } /** Test creating a client with valid info */ @Test public void testConstruction() { - new ScrollCaptureConnection(mTarget1, mConnectionCallbacks); + ScrollCaptureTarget target = new ScrollCaptureTarget( + mView, mLocalVisibleRect, mPositionInWindow, mCallback); + target.setScrollBounds(new Rect(1, 2, 3, 4)); + new ScrollCaptureConnection(Runnable::run, target, mRemote); } /** Test creating a client fails if arguments are not valid. */ @Test public void testConstruction_requiresScrollBounds() { try { - mTarget1.setScrollBounds(null); - new ScrollCaptureConnection(mTarget1, mConnectionCallbacks); + mTarget.setScrollBounds(null); + new ScrollCaptureConnection(Runnable::run, mTarget, mRemote); fail("An exception was expected."); } catch (RuntimeException ex) { // Ignore, expected. } } - @SuppressWarnings("SameParameterValue") - private static Answer<Void> runRunnable(int arg) { - return invocation -> { - Runnable r = invocation.getArgument(arg); - r.run(); - return null; - }; - } - - @SuppressWarnings("SameParameterValue") - private static Answer<Void> reportBufferSent(int sessionArg, long frameNum, Rect capturedArea) { - return invocation -> { - ScrollCaptureSession session = invocation.getArgument(sessionArg); - session.notifyBufferSent(frameNum, capturedArea); - return null; - }; - } - /** @see ScrollCaptureConnection#startCapture(Surface) */ @Test public void testStartCapture() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - - // Have the session start accepted immediately - doAnswer(runRunnable(1)).when(mCallback1) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - connection.startCapture(mSurface); - getInstrumentation().waitForIdleSync(); - - verify(mCallback1, times(1)) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - verify(mConnectionCallbacks, times(1)).onCaptureStarted(); - verifyNoMoreInteractions(mConnectionCallbacks); + mConnection.startCapture(mSurface); + + mCallback.completeStartRequest(); + assertTrue(mConnection.isStarted()); + + verify(mRemote, times(1)).onCaptureStarted(); + verifyNoMoreInteractions(mRemote); } @Test - public void testStartCaptureTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - connection.startCapture(mSurface); + public void testStartCapture_cancellation() throws Exception { + ICancellationSignal signal = mConnection.startCapture(mSurface); + signal.cancel(); - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); + mCallback.completeStartRequest(); + assertFalse(mConnection.isStarted()); - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - } - - private void startCapture(ScrollCaptureConnection connection) throws Exception { - doAnswer(runRunnable(1)).when(mCallback1) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - connection.startCapture(mSurface); - getInstrumentation().waitForIdleSync(); - reset(mCallback1, mConnectionCallbacks); + verifyNoMoreInteractions(mRemote); } /** @see ScrollCaptureConnection#requestImage(Rect) */ @Test public void testRequestImage() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Stub the callback to complete the request immediately - doAnswer(reportBufferSent(/* sessionArg */ 0, /* frameNum */ 1L, new Rect(1, 2, 3, 4))) - .when(mCallback1) - .onScrollCaptureImageRequest(any(ScrollCaptureSession.class), any(Rect.class)); - - // Make the inbound binder call - connection.requestImage(new Rect(1, 2, 3, 4)); + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureImageRequest( - any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4))); + mConnection.requestImage(new Rect(1, 2, 3, 4)); + mCallback.completeImageRequest(new Rect(1, 2, 3, 4)); - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)) - .onCaptureBufferSent(eq(1L), eq(new Rect(1, 2, 3, 4))); - - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + verify(mRemote, times(1)) + .onImageRequestCompleted(eq(0), eq(new Rect(1, 2, 3, 4))); + verifyNoMoreInteractions(mRemote); } @Test - public void testRequestImageTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Make the inbound binder call - connection.requestImage(new Rect(1, 2, 3, 4)); - - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureImageRequest( - any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4))); - - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); - getInstrumentation().waitForIdleSync(); - - // (callback not stubbed, does nothing) - // Timeout triggers request to end capture - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + public void testRequestImage_cancellation() throws Exception { + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); + + ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4)); + signal.cancel(); + mCallback.completeImageRequest(new Rect(1, 2, 3, 4)); + + verifyNoMoreInteractions(mRemote); } /** @see ScrollCaptureConnection#endCapture() */ @Test public void testEndCapture() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Stub the callback to complete the request immediately - doAnswer(runRunnable(0)) - .when(mCallback1) - .onScrollCaptureEnd(any(Runnable.class)); - - // Make the inbound binder call - connection.endCapture(); + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); + mConnection.endCapture(); + mCallback.completeEndRequest(); - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)).onConnectionClosed(); - - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + // And the reply is sent + verify(mRemote, times(1)).onCaptureEnded(); + verifyNoMoreInteractions(mRemote); } + /** @see ScrollCaptureConnection#endCapture() */ @Test - public void testEndCaptureTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); + public void testEndCapture_cancellation() throws Exception { + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Make the inbound binder call - connection.endCapture(); + ICancellationSignal signal = mConnection.endCapture(); + signal.cancel(); + mCallback.completeEndRequest(); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); - - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)).onConnectionClosed(); + verifyNoMoreInteractions(mRemote); + } - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + @Test + public void testClose() throws Exception { + mConnection.close(); + assertFalse(mConnection.isConnected()); + verifyNoMoreInteractions(mRemote); } + } diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java new file mode 100644 index 000000000000..cc229e11dcf2 --- /dev/null +++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java @@ -0,0 +1,415 @@ +/* + * 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.view; + +import static androidx.test.InstrumentationRegistry.getTargetContext; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.CancellationSignal; +import android.os.SystemClock; + +import androidx.annotation.NonNull; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Tests of {@link ScrollCaptureTargetSelector}. + */ +@RunWith(AndroidJUnit4.class) +public class ScrollCaptureSearchResultsTest { + + + private static final Rect EMPTY_RECT = new Rect(); + private static final String TAG = "Test"; + + private final Executor mDirectExec = Runnable::run; + private Executor mBgExec; + + @Before + public void setUp() { + mBgExec = Executors.newSingleThreadExecutor(); + } + + @Test + public void testNoTargets() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + assertTrue(results.isComplete()); + + assertNull("Expected null due to empty queue", results.getTopResult()); + } + + @Test + public void testNoValidTargets() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec); + callback1.setScrollBounds(EMPTY_RECT); + ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), + new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // Supplies scrollBounds = empty rect + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec); + callback2.setScrollBounds(EMPTY_RECT); + ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), + new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); + + results.addTarget(target1); + results.addTarget(target2); + + assertTrue(results.isComplete()); + assertNull("Expected null due to no valid targets", results.getTopResult()); + } + + @Test + public void testSingleTarget() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback(mDirectExec); + ScrollCaptureTarget target = createTarget(callback, + new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + callback.setScrollBounds(new Rect(2, 2, 18, 18)); + + results.addTarget(target); + assertTrue(results.isComplete()); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Excepted the same target as a result", target, result); + assertEquals("result has wrong scroll bounds", + new Rect(2, 2, 18, 18), result.getScrollBounds()); + } + + @Test + public void testSingleTarget_backgroundThread() throws InterruptedException { + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec); + ScrollCaptureTarget target1 = createTarget(callback1, + new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + callback1.setDelay(100); + callback1.setScrollBounds(new Rect(2, 2, 18, 18)); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + + CountDownLatch latch = new CountDownLatch(1); + results.setOnCompleteListener(latch::countDown); + if (!latch.await(200, TimeUnit.MILLISECONDS)) { + fail("onComplete listener was expected"); + } + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Excepted the single target1 as a result", target1, result); + assertEquals("Result has wrong scroll bounds", + new Rect(2, 2, 18, 18), result.getScrollBounds()); + } + + @Test + public void testRanking() { + + // 1 - Empty + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec); + callback1.setScrollBounds(EMPTY_RECT); + ViewGroup targetView1 = new FakeView(getTargetContext(), 0, 0, 60, 60, 1); + ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 2 - 10x10 + HINT_INCLUDE + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec); + callback2.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 0, 60, 60, 2); + ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_INCLUDE); + + // 3 - 20x20 + AUTO + FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec); + callback3.setScrollBounds(new Rect(0, 0, 20, 20)); + ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 0, 60, 60, 3); + ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 4 - 30x30 + AUTO + FakeScrollCaptureCallback callback4 = new FakeScrollCaptureCallback(mDirectExec); + callback4.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 0, 60, 60, 4); + ScrollCaptureTarget target4 = createTargetWithView(targetView4, callback4, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 5 - 10x10 + child of #4 + FakeScrollCaptureCallback callback5 = new FakeScrollCaptureCallback(mDirectExec); + callback5.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 60, 5); + ScrollCaptureTarget target5 = createTargetWithView(targetView5, callback5, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + targetView4.addView(targetView5); + + // 6 - 20x20 + child of #4 + FakeScrollCaptureCallback callback6 = new FakeScrollCaptureCallback(mDirectExec); + callback6.setScrollBounds(new Rect(0, 0, 20, 20)); + ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 0, 60, 60, 6); + ScrollCaptureTarget target6 = createTargetWithView(targetView6, callback6, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + targetView4.addView(targetView6); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + results.addTarget(target2); + results.addTarget(target3); + results.addTarget(target4); + results.addTarget(target5); + results.addTarget(target6); + assertTrue(results.isComplete()); + + // Verify "top" result + assertEquals(target2, results.getTopResult()); + + // Verify priority ("best" first) + assertThat(results.getTargets()) + .containsExactly( + target2, + target6, + target5, + target4, + target3, + target1); + } + + /** + * If a timeout expires, late results are ignored. + */ + @Test + public void testTimeout() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + + // callback 1, 10x10, hint=AUTO, responds after 100ms from bg thread + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec); + callback1.setScrollBounds(new Rect(5, 5, 15, 15)); + callback1.setDelay(100); + ScrollCaptureTarget target1 = createTarget( + callback1, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + results.addTarget(target1); + + // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mBgExec); + callback2.setScrollBounds(new Rect(0, 0, 20, 20)); + callback2.setDelay(1000); + ScrollCaptureTarget target2 = createTarget( + callback2, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + results.addTarget(target2); + + // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread + FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mBgExec); + callback3.setScrollBounds(new Rect(0, 0, 20, 20)); + callback3.setDelay(1500); + ScrollCaptureTarget target3 = createTarget( + callback3, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_INCLUDE); + results.addTarget(target3); + + // callback 1 will be received + // callback 2 & 3 will be ignored due to timeout + SystemClock.sleep(500); + results.finish(); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Expected target1 as the result, due to timeouts of others", target1, result); + assertEquals("callback1 should have been called", + 1, callback1.getOnScrollCaptureSearchCount()); + assertEquals("callback2 should have been called", + 1, callback2.getOnScrollCaptureSearchCount()); + assertEquals("callback3 should have been called", + 1, callback3.getOnScrollCaptureSearchCount()); + + assertEquals("result has wrong scroll bounds", + new Rect(5, 5, 15, 15), result.getScrollBounds()); + assertNull("target2 should not have been updated", + target2.getScrollBounds()); + assertNull("target3 should not have been updated", + target3.getScrollBounds()); + } + + @Test + public void testWithCallbackMultipleReplies() { + // Calls response methods 3 times each + ScrollCaptureCallback callback1 = new CallbackStub() { + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + onReady.accept(new Rect(1, 2, 3, 4)); + onReady.accept(new Rect(9, 10, 11, 12)); + } + }; + + ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), + new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + assertTrue(results.isComplete()); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Expected target1", target1, result); + assertEquals("result has wrong scroll bounds", + new Rect(1, 2, 3, 4), result.getScrollBounds()); + } + + private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) { + view.setScrollCaptureHint(scrollCaptureHint); + view.onVisibilityAggregated(true); + // Treat any offset as padding, outset localVisibleRect on all sides and use this as + // child bounds + Rect bounds = new Rect(localVisibleRect); + bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top); + view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom); + view.onVisibilityAggregated(true); + } + + private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect, + Point positionInWindow, int scrollCaptureHint) { + View mockView = new View(getTargetContext()); + return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow, + scrollCaptureHint); + } + + private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback, + Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) { + setupTargetView(view, localVisibleRect, scrollCaptureHint); + return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback); + } + + + static class FakeView extends ViewGroup implements ViewParent { + FakeView(Context context, int l, int t, int r, int b, int id) { + super(context); + layout(l, t, r, b); + setId(id); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + } + } + + static class FakeScrollCaptureCallback implements ScrollCaptureCallback { + private final Executor mExecutor; + private Rect mScrollBounds; + private long mDelayMillis; + private int mOnScrollCaptureSearchCount; + FakeScrollCaptureCallback(Executor executor) { + mExecutor = executor; + } + public int getOnScrollCaptureSearchCount() { + return mOnScrollCaptureSearchCount; + } + + @Override + public void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) { + mOnScrollCaptureSearchCount++; + run(() -> { + Rect b = getScrollBounds(); + onReady.accept(b); + }); + } + + @Override + public void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal, + Runnable onReady) { + run(onReady); + } + + @Override + public void onScrollCaptureImageRequest(ScrollCaptureSession session, + CancellationSignal signal, Rect captureArea, Consumer<Rect> onReady) { + run(() -> onReady.accept(captureArea)); + } + + @Override + public void onScrollCaptureEnd(Runnable onReady) { + run(onReady); + } + + public void setScrollBounds(@Nullable Rect scrollBounds) { + mScrollBounds = scrollBounds; + } + + public void setDelay(long delayMillis) { + mDelayMillis = delayMillis; + } + + protected Rect getScrollBounds() { + return mScrollBounds; + } + + protected void run(Runnable r) { + mExecutor.execute(() -> { + delay(); + r.run(); + }); + } + + protected void delay() { + if (mDelayMillis > 0) { + try { + Thread.sleep(mDelayMillis); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + static class CallbackStub implements ScrollCaptureCallback { + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + } + } +} diff --git a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java b/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java deleted file mode 100644 index 8b21b8ecee89..000000000000 --- a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import static androidx.test.InstrumentationRegistry.getTargetContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.LinkedList; -import java.util.function.Consumer; - -/** - * Tests of {@link ScrollCaptureTargetResolver}. - */ -@RunWith(AndroidJUnit4.class) -public class ScrollCaptureTargetResolverTest { - - private static final long TEST_TIMEOUT_MS = 2000; - private static final long RESOLVER_TIMEOUT_MS = 1000; - - private Handler mHandler; - private TargetConsumer mTargetConsumer; - - @Before - public void setUp() { - mTargetConsumer = new TargetConsumer(); - mHandler = new Handler(getTargetContext().getMainLooper()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testEmptyQueue() throws InterruptedException { - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(new LinkedList<>()); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertNull("Expected null due to empty queue", result); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testNoValidTargets() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - // Supplies scrollBounds = null - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(null); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - // Supplies scrollBounds = empty rect - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect()); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - targetQueue.add(target1); - targetQueue.add(target2); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertNull("Expected null due to no valid targets", result); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testSingleTarget() throws InterruptedException { - FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback(); - ScrollCaptureTarget target = createTarget(callback, - new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - callback.setScrollBounds(new Rect(2, 2, 18, 18)); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target); - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Excepted the same target as a result", target, result); - assertEquals("result has wrong scroll bounds", - new Rect(2, 2, 18, 18), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testSingleTarget_backgroundThread() throws InterruptedException { - BackgroundTestCallback callback1 = new BackgroundTestCallback(); - ScrollCaptureTarget target1 = createTarget(callback1, - new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - callback1.setDelay(100); - callback1.setScrollBounds(new Rect(2, 2, 18, 18)); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target1); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Excepted the single target1 as a result", target1, result); - assertEquals("Result has wrong scroll bounds", - new Rect(2, 2, 18, 18), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testPreferNonEmptyBounds() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect()); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(null); - ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50), - new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); - targetQueue.add(target2); // scrollBounds not null or empty() - targetQueue.add(target3); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertEquals("Expected " + target2 + " as a result", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 20, 20), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testPreferHintInclude() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(1, 1, 19, 19)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(new Rect(2, 2, 18, 18)); - ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50), - new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); - targetQueue.add(target2); // * INCLUDE > AUTO - targetQueue.add(target3); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertEquals("input = " + targetQueue + " Expected " + target2 - + " as the result, due to hint=INCLUDE", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(1, 1, 19, 19), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testDescendantPreferred() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - ViewGroup targetView1 = new FakeRootView(getTargetContext(), 0, 0, 60, 60); // 60x60 - ViewGroup targetView2 = new FakeRootView(getTargetContext(), 20, 30, 40, 50); // 20x20 - ViewGroup targetView3 = new FakeRootView(getTargetContext(), 5, 5, 15, 15); // 10x10 - - targetView1.addView(targetView2); - targetView2.addView(targetView3); - - // Create first target with an unrelated parent - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect(0, 0, 60, 60)); - ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1, - new Rect(0, 0, 60, 60), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - // Create second target associated with a view within parent2 - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2, - new Rect(0, 0, 20, 20), - new Point(20, 30), View.SCROLL_CAPTURE_HINT_AUTO); - - // Create third target associated with a view within parent3 - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(new Rect(0, 0, 15, 15)); - ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3, - new Rect(0, 0, 15, 15), - new Point(25, 35), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); // auto, 60x60 - targetQueue.add(target2); // auto, 20x20 - targetQueue.add(target3); // auto, 15x15 <- innermost scrollable - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target3 as the result, due to relation", target3, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 15, 15), result.getScrollBounds()); - } - - /** - * If a timeout expires, late results are ignored. - */ - @Test(timeout = TEST_TIMEOUT_MS) - public void testTimeout() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - // callback 1, 10x10, hint=AUTO, responds immediately from bg thread - BackgroundTestCallback callback1 = new BackgroundTestCallback(); - callback1.setScrollBounds(new Rect(5, 5, 15, 15)); - ScrollCaptureTarget target1 = createTarget( - callback1, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - targetQueue.add(target1); - - // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread - BackgroundTestCallback callback2 = new BackgroundTestCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - callback2.setDelay(5000); - ScrollCaptureTarget target2 = createTarget( - callback2, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - targetQueue.add(target2); - - // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread - BackgroundTestCallback callback3 = new BackgroundTestCallback(); - callback3.setScrollBounds(new Rect(0, 0, 20, 20)); - callback3.setDelay(10000); - ScrollCaptureTarget target3 = createTarget( - callback3, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_INCLUDE); - targetQueue.add(target3); - - // callback 1 will be received - // callback 2 & 3 will be ignored due to timeout - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target1 as the result, due to timeouts of others", target1, result); - assertEquals("result has wrong scroll bounds", - new Rect(5, 5, 15, 15), result.getScrollBounds()); - assertEquals("callback1 should have been called", - 1, callback1.getOnScrollCaptureSearchCount()); - assertEquals("callback2 should have been called", - 1, callback2.getOnScrollCaptureSearchCount()); - assertEquals("callback3 should have been called", - 1, callback3.getOnScrollCaptureSearchCount()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testWithCallbackMultipleReplies() throws InterruptedException { - // Calls response methods 3 times each - RepeatingCaptureCallback callback1 = new RepeatingCaptureCallback(3); - callback1.setScrollBounds(new Rect(2, 2, 18, 18)); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target1); - targetQueue.add(target2); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target2 as the result, due to hint=INCLUDE", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 20, 20), result.getScrollBounds()); - assertEquals("callback1 should have been called once", - 1, callback1.getOnScrollCaptureSearchCount()); - assertEquals("callback2 should have been called once", - 1, callback2.getOnScrollCaptureSearchCount()); - } - - private static class TargetConsumer implements Consumer<ScrollCaptureTarget> { - volatile ScrollCaptureTarget mResult; - int mAcceptCount; - - ScrollCaptureTarget getLastValue() { - return mResult; - } - - int acceptCount() { - return mAcceptCount; - } - - @Override - public void accept(@Nullable ScrollCaptureTarget t) { - mAcceptCount++; - mResult = t; - } - } - - private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) { - view.setScrollCaptureHint(scrollCaptureHint); - view.onVisibilityAggregated(true); - // Treat any offset as padding, outset localVisibleRect on all sides and use this as - // child bounds - Rect bounds = new Rect(localVisibleRect); - bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top); - view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom); - view.onVisibilityAggregated(true); - } - - private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect, - Point positionInWindow, int scrollCaptureHint) { - View mockView = new View(getTargetContext()); - return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow, - scrollCaptureHint); - } - - private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback, - Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) { - setupTargetView(view, localVisibleRect, scrollCaptureHint); - return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback); - } - - - static class FakeRootView extends ViewGroup implements ViewParent { - FakeRootView(Context context, int l, int t, int r, int b) { - super(context); - layout(l, t, r, b); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - } - } - - static class FakeScrollCaptureCallback implements ScrollCaptureCallback { - private Rect mScrollBounds; - private long mDelayMillis; - private int mOnScrollCaptureSearchCount; - - public int getOnScrollCaptureSearchCount() { - return mOnScrollCaptureSearchCount; - } - - @Override - public void onScrollCaptureSearch(Consumer<Rect> onReady) { - mOnScrollCaptureSearchCount++; - run(() -> { - Rect b = getScrollBounds(); - onReady.accept(b); - }); - } - - @Override - public void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) { - run(onReady); - } - - @Override - public void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect captureArea) { - run(() -> session.notifyBufferSent(0, captureArea)); - } - - @Override - public void onScrollCaptureEnd(Runnable onReady) { - run(onReady); - } - - public void setScrollBounds(@Nullable Rect scrollBounds) { - mScrollBounds = scrollBounds; - } - - public void setDelay(long delayMillis) { - mDelayMillis = delayMillis; - } - - protected Rect getScrollBounds() { - return mScrollBounds; - } - - protected void run(Runnable r) { - delay(); - r.run(); - } - - protected void delay() { - if (mDelayMillis > 0) { - try { - Thread.sleep(mDelayMillis); - } catch (InterruptedException e) { - // Ignore - } - } - } - } - - static class RepeatingCaptureCallback extends FakeScrollCaptureCallback { - private int mRepeatCount; - - RepeatingCaptureCallback(int repeatCount) { - mRepeatCount = repeatCount; - } - - protected void run(Runnable r) { - delay(); - for (int i = 0; i < mRepeatCount; i++) { - r.run(); - } - } - } - - /** Response to async calls on an arbitrary background thread */ - static class BackgroundTestCallback extends FakeScrollCaptureCallback { - static int sCount = 0; - private void runOnBackgroundThread(Runnable r) { - final Runnable target = () -> { - delay(); - r.run(); - }; - Thread t = new Thread(target); - synchronized (BackgroundTestCallback.this) { - sCount++; - } - t.setName("Background-Thread-" + sCount); - t.start(); - } - - @Override - protected void run(Runnable r) { - runOnBackgroundThread(r); - } - } -} diff --git a/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java b/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java new file mode 100644 index 000000000000..45ffb1221515 --- /dev/null +++ b/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java @@ -0,0 +1,54 @@ +/* + * 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.view; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SoundEffectConstants} + * + * Build/Install/Run: + * atest FrameworksCoreTests:SoundEffectConstantsTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SoundEffectConstantsTest { + + @Test + public void testIsNavigationRepeat() { + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_RIGHT)); + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_LEFT)); + assertTrue( + SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_REPEAT_UP)); + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_DOWN)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_RIGHT)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_LEFT)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_UP)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_DOWN)); + assertFalse(SoundEffectConstants.isNavigationRepeat(-1)); + } +} diff --git a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java b/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java new file mode 100644 index 000000000000..36104cf0f71d --- /dev/null +++ b/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java @@ -0,0 +1,46 @@ +/* + * 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.view; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class SurfaceControlFpsListenerTest { + + @Test + public void registersAndUnregisters() { + + SurfaceControlFpsListener listener = new SurfaceControlFpsListener() { + @Override + public void onFpsReported(float fps) { + // Ignore + } + }; + + listener.register(new SurfaceControl()); + + listener.unregister(); + } +} diff --git a/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java new file mode 100644 index 000000000000..1520c6e34a95 --- /dev/null +++ b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java @@ -0,0 +1,86 @@ +/* + * 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.view; + +import static org.junit.Assert.*; + +import android.graphics.Rect; +import android.os.CancellationSignal; + +import androidx.annotation.NonNull; + +import java.util.function.Consumer; + +class TestScrollCaptureCallback implements ScrollCaptureCallback { + private Consumer<Rect> mSearchConsumer; + private Runnable mStartOnReady; + private Consumer<Rect> mImageOnComplete; + private Runnable mOnEndReady; + private volatile int mModCount; + + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + mSearchConsumer = onReady; + mModCount++; + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + mStartOnReady = onReady; + mModCount++; + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + @NonNull Consumer<Rect> onComplete) { + mImageOnComplete = onComplete; + mModCount++; + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + mOnEndReady = onReady; + } + + void completeSearchRequest(Rect scrollBounds) { + assertNotNull("Did not receive search request", mSearchConsumer); + mSearchConsumer.accept(scrollBounds); + mModCount++; + } + + void verifyZeroInteractions() { + assertEquals("Expected zero interactions", 0, mModCount); + } + + void completeStartRequest() { + assertNotNull("Did not receive start request", mStartOnReady); + mStartOnReady.run(); + } + + void completeImageRequest(Rect captured) { + assertNotNull("Did not receive image request", mImageOnComplete); + mImageOnComplete.accept(captured); + } + + void completeEndRequest() { + assertNotNull("Did not receive end request", mOnEndReady); + mOnEndReady.run(); + } +} diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java index 3af0533e763c..41cd4c562bd8 100644 --- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java @@ -19,7 +19,9 @@ package android.view; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.testng.AssertJUnit.assertSame; @@ -27,19 +29,20 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.CancellationSignal; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.LinkedList; -import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Exercises Scroll Capture search in {@link ViewGroup}. @@ -50,10 +53,7 @@ import java.util.Queue; @RunWith(MockitoJUnitRunner.class) public class ViewGroupScrollCaptureTest { - @Mock - ScrollCaptureCallback mMockCallback; - @Mock - ScrollCaptureCallback mMockCallback2; + private static final Executor DIRECT_EXECUTOR = Runnable::run; /** Make sure the hint flags are saved and loaded correctly. */ @Test @@ -103,25 +103,24 @@ public class ViewGroupScrollCaptureTest { public void testDispatchScrollCaptureSearch_noCallback_hintAuto() throws Exception { final Context context = getInstrumentation().getContext(); final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Verify the system checked for fallback support - viewGroup.assertDispatchScrollCaptureCount(1); - viewGroup.assertLastDispatchScrollCaptureArgs(localVisibleRect, windowOffset); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.completeSearchRequest(new Rect(1, 2, 3, 4)); + assertTrue(results.isComplete()); // Verify the target is as expected. - assertEquals(1, targetList.size()); - ScrollCaptureTarget target = targetList.get(0); - assertSame("Target has the wrong callback", mMockCallback, target.getCallback()); + ScrollCaptureTarget target = results.getTopResult(); + assertNotNull("Target not found", target); + assertSame("Target has the wrong callback", callback, target.getCallback()); assertSame("Target has the wrong View", viewGroup, target.getContainingView()); assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, target.getContainingView().getScrollCaptureHint()); @@ -139,18 +138,22 @@ public class ViewGroupScrollCaptureTest { final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); + assertTrue(results.isComplete()); // Dispatch - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.verifyZeroInteractions(); // Verify the results. - assertEquals("Target list size should be zero.", 0, targetList.size()); + assertTrue("Results should be empty.", results.isEmpty()); } /** @@ -164,27 +167,34 @@ public class ViewGroupScrollCaptureTest { final Context context = getInstrumentation().getContext(); MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback(); + // With an already provided scroll capture callback - viewGroup.setScrollCaptureCallback(mMockCallback); + viewGroup.setScrollCaptureCallback(callback); // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback2); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Confirm that framework support was not requested, - // because this view already had a callback set. - viewGroup.assertCreateScrollCaptureCallbackInternalCount(0); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.completeSearchRequest(new Rect(1, 2, 3, 4)); // Verify the target is as expected. - assertEquals(1, targetList.size()); - ScrollCaptureTarget target = targetList.get(0); - assertSame("Target has the wrong callback", mMockCallback, target.getCallback()); + assertFalse(results.isEmpty()); + assertTrue(results.isComplete()); + + // internal framework callback was not requested + callback2.verifyZeroInteractions(); + + ScrollCaptureTarget target = results.getTopResult(); + + assertNotNull("Target not found", target); + assertSame("Target has the wrong callback", callback, target.getCallback()); assertSame("Target has the wrong View", viewGroup, target.getContainingView()); assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, target.getContainingView().getScrollCaptureHint()); @@ -201,22 +211,22 @@ public class ViewGroupScrollCaptureTest { final Context context = getInstrumentation().getContext(); MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE); + + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + // With an already provided scroll capture callback - viewGroup.setScrollCaptureCallback(mMockCallback); + viewGroup.setScrollCaptureCallback(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup itself - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Confirm that framework support was not requested, because this view is excluded. - // (And because this view has a callback set.) - viewGroup.assertCreateScrollCaptureCallbackInternalCount(0); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.verifyZeroInteractions(); // Has callback, but hint=excluded, so excluded. - assertTrue(targetList.isEmpty()); + assertNull(results.getTopResult()); } /** @@ -252,37 +262,43 @@ public class ViewGroupScrollCaptureTest { // | | | // +---------------+----------+ (200,200) - // View 1 is clipped and not visible. + // View 1 is fully clipped and not visible. final MockView view1 = new MockView(context, 0, 0, 200, 25); viewGroup.addView(view1); - // View 2 is partially visible. + // View 2 is partially visible. (75x75) final MockView view2 = new MockView(context, 0, 25, 150, 100); viewGroup.addView(view2); - // View 3 is partially visible. + TestScrollCaptureCallback callback1 = new TestScrollCaptureCallback(); + + // View 3 is partially visible (175x50) // Pretend View3 can scroll by having framework provide fallback support final MockView view3 = new MockView(context, 0, 100, 200, 200); // When system internal scroll capture is requested for this view, return this callback. - view3.setScrollCaptureCallbackInternalForTest(mMockCallback); + view3.setScrollCaptureCallbackInternalForTest(callback1); viewGroup.addView(view3); // View 4 is invisible and should be ignored. final MockView view4 = new MockView(context, 150, 25, 200, 100, View.INVISIBLE); viewGroup.addView(view4); - // View 4 is invisible and should be ignored. + TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback(); + + // View 5 is partially visible and explicitly included via flag. (25x50) final MockView view5 = new MockView(context, 150, 100, 200, 200); - // When system internal scroll capture is requested for this view, return this callback. - view5.setScrollCaptureCallback(mMockCallback2); + view5.setScrollCaptureCallback(callback2); view5.setScrollCaptureHint(View.SCROLL_CAPTURE_HINT_INCLUDE); viewGroup.addView(view5); // Where targets are added - final LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + final ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback1.completeSearchRequest(new Rect(0, 0, 200, 100)); + callback2.completeSearchRequest(new Rect(0, 0, 50, 100)); + assertTrue(results.isComplete()); // View 1 is entirely clipped by the parent and not visible, dispatch // skips this view entirely. @@ -317,18 +333,14 @@ public class ViewGroupScrollCaptureTest { view5.assertCreateScrollCaptureCallbackInternalCount(0); // 2 views should have been returned, view3 & view5 - assertEquals(2, targetList.size()); - - ScrollCaptureTarget target = targetList.get(0); - assertSame("First target has the wrong View", view3, target.getContainingView()); - assertSame("First target has the wrong callback", mMockCallback, target.getCallback()); - assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, - target.getContainingView().getScrollCaptureHint()); - - target = targetList.get(1); - assertSame("Second target has the wrong View", view5, target.getContainingView()); - assertSame("Second target has the wrong callback", mMockCallback2, target.getCallback()); - assertEquals("Second target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE, + assertFalse(results.isEmpty()); + assertTrue(results.isComplete()); + + ScrollCaptureTarget target = results.getTopResult(); + assertNotNull("Target not found", target); + assertSame("Result is the wrong View", view5, target.getContainingView()); + assertSame("Result is the wrong callback", callback2, target.getCallback()); + assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE, target.getContainingView().getScrollCaptureHint()); } @@ -371,7 +383,7 @@ public class ViewGroupScrollCaptureTest { } void assertCreateScrollCaptureCallbackInternalCount(int count) { - assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal", + assertEquals("Unexpected number of calls to createScrollCaptureCallbackInternal", count, mCreateScrollCaptureCallbackInternalCount); } @@ -385,11 +397,11 @@ public class ViewGroupScrollCaptureTest { @Override public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset, - Queue<ScrollCaptureTarget> targets) { + Consumer<ScrollCaptureTarget> results) { mDispatchScrollCaptureSearchNumCalls++; mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect); mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset); - super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); + super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results); } @Override @@ -401,13 +413,31 @@ public class ViewGroupScrollCaptureTest { } } + static class CallbackStub implements ScrollCaptureCallback { + + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + Consumer<Rect> onComplete) { + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + } + }; + public static final class MockViewGroup extends ViewGroup { private ScrollCaptureCallback mInternalCallback; - private int mDispatchScrollCaptureSearchNumCalls; - private Rect mDispatchScrollCaptureSearchLastLocalVisibleRect; - private Point mDispatchScrollCaptureSearchLastWindowOffset; - private int mCreateScrollCaptureCallbackInternalCount; - MockViewGroup(Context context) { this(context, /* left */ 0, /* top */0, /* right */ 0, /* bottom */0); @@ -428,16 +458,10 @@ public class ViewGroupScrollCaptureTest { mInternalCallback = internal; } - void assertDispatchScrollCaptureSearchCount(int count) { - assertEquals("Unexpected number of calls to dispatchScrollCaptureSearch", - count, mDispatchScrollCaptureSearchNumCalls); - } - @Override @Nullable public ScrollCaptureCallback createScrollCaptureCallbackInternal(Rect localVisibleRect, Point offsetInWindow) { - mCreateScrollCaptureCallbackInternalCount++; return mInternalCallback; } @@ -445,36 +469,5 @@ public class ViewGroupScrollCaptureTest { protected void onLayout(boolean changed, int l, int t, int r, int b) { // We don't layout this view. } - - void assertDispatchScrollCaptureCount(int count) { - assertEquals(count, mDispatchScrollCaptureSearchNumCalls); - } - - void assertLastDispatchScrollCaptureArgs(Rect localVisibleRect, Point windowOffset) { - assertEquals("arg localVisibleRect to dispatchScrollCaptureCallback was incorrect.", - localVisibleRect, mDispatchScrollCaptureSearchLastLocalVisibleRect); - assertEquals("arg windowOffset to dispatchScrollCaptureCallback was incorrect.", - windowOffset, mDispatchScrollCaptureSearchLastWindowOffset); - } - void assertCreateScrollCaptureCallbackInternalCount(int count) { - assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal", - count, mCreateScrollCaptureCallbackInternalCount); - } - - void reset() { - mDispatchScrollCaptureSearchNumCalls = 0; - mDispatchScrollCaptureSearchLastWindowOffset = null; - mDispatchScrollCaptureSearchLastLocalVisibleRect = null; - mCreateScrollCaptureCallbackInternalCount = 0; - } - - @Override - public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset, - Queue<ScrollCaptureTarget> targets) { - mDispatchScrollCaptureSearchNumCalls++; - mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect); - mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset); - super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); - } } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index c67174f0ae1e..7746bc2e273a 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -207,7 +207,7 @@ public class ViewRootImplTest { final CountDownLatch latch = new CountDownLatch(1); mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() { @Override - public void onUnavailable() { + public void onScrollCaptureResponse(ScrollCaptureResponse response) { latch.countDown(); } }); @@ -220,6 +220,37 @@ public class ViewRootImplTest { } /** + * Ensure scroll capture request handles a ViewRootImpl with no view tree. + */ + @Test + public void requestScrollCapture_timeout() { + final View view = new View(mContext); + view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowManager.LayoutParams wmlp = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + // Set a fake token to bypass 'is your activity running' check + wmlp.token = new Binder(); + view.setLayoutParams(wmlp); + mViewRootImpl.setView(view, wmlp, null); + }); + + final CountDownLatch latch = new CountDownLatch(1); + mViewRootImpl.setScrollCaptureRequestTimeout(100); + mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() { + @Override + public void onScrollCaptureResponse(ScrollCaptureResponse response) { + latch.countDown(); + } + }); + try { + if (!latch.await(2500, TimeUnit.MILLISECONDS)) { + fail("requestScrollCapture timeout did not occur"); + } + } catch (InterruptedException e) { /* ignore */ } + } + + /** * When window doesn't have focus, keys should be dropped. */ @Test diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 10aaf317fb49..9cb7876b3e5a 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -105,7 +105,8 @@ public class FrameTrackerTest { mTracker = Mockito.spy( new FrameTracker(session, handler, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, - /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); + /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1, + null)); doNothing().when(mTracker).triggerPerfetto(); } diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index c4c475b6a0e9..8f4948c02a74 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -96,7 +96,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, - /*traceThresholdFrameTimeMillis=*/ -1)); + /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); // Simulate a trace session and see if begin / end are invoked. @@ -123,21 +123,12 @@ public class InteractionJankMonitorTest { @Test public void testCheckInitState() { InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker); + View view = new View(mActivity); + assertThat(view.isAttachedToWindow()).isFalse(); - // Should return false if invoking begin / end without init invocation. - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + // Should return false if the view passed in is not attached to window yet. + assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); - - // Everything should be fine if invoking init first. - boolean thrown = false; - try { - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); - } catch (Exception ex) { - thrown = true; - } finally { - assertThat(thrown).isFalse(); - } } @Test @@ -152,7 +143,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, - /*traceThresholdFrameTimeMillis=*/ -1)); + /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); assertThat(monitor.begin(mView, session.getCuj())).isTrue(); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java index 9cac7e794965..ff728d651067 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java @@ -77,6 +77,7 @@ import java.util.Arrays; * bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest */ @SmallTest +@SkipPresubmit("b/180015146") @RunWith(AndroidJUnit4.class) public class BatteryStatsCpuTimesTest { @Mock diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index 4b37dd226e69..24baa93337ba 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -73,6 +73,7 @@ public class BatteryStatsImplTest { } @Test + @SkipPresubmit("b/180015146") public void testUpdateProcStateCpuTimes() { mBatteryStatsImpl.setOnBatteryInternal(true); mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0); @@ -230,6 +231,7 @@ public class BatteryStatsImplTest { } @Test + @SkipPresubmit("b/180015146") public void testCopyFromAllUidsCpuTimes() { mBatteryStatsImpl.setOnBatteryInternal(false); mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index 6652c64c4344..931611ea7478 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -295,6 +295,7 @@ public class BatteryStatsNoteTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testAlarmStartAndFinishLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -332,6 +333,7 @@ public class BatteryStatsNoteTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testAlarmStartAndFinishLocked_workSource() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java index 3b27f1897bd2..dd814e651ede 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java @@ -56,6 +56,7 @@ public class BatteryStatsSamplingTimerTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testEndSampleAndContinueWhenTimeOrCountDecreases() throws Exception { final MockClocks clocks = new MockClocks(); final BatteryStatsImpl.TimeBase timeBase = Mockito.mock(BatteryStatsImpl.TimeBase.class); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index b819d9edb2a8..d276bc34d05a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -40,6 +40,7 @@ import org.junit.runners.Suite; BatteryStatsTimeBaseTest.class, BatteryStatsTimerTest.class, BatteryStatsUidTest.class, + BatteryUsageStatsProviderTest.class, BatteryUsageStatsTest.class, BatteryStatsUserLifecycleTests.class, BluetoothPowerCalculatorTest.class, @@ -71,9 +72,9 @@ import org.junit.runners.Suite; UserPowerCalculatorTest.class, VideoPowerCalculatorTest.class, WakelockPowerCalculatorTest.class, + WifiPowerCalculatorTest.class, com.android.internal.power.MeasuredEnergyStatsTest.class }) public class BatteryStatsTests { -} - +}
\ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java index e7a1bcae459a..e90bcb76e457 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java @@ -78,6 +78,7 @@ public class BatteryStatsUserLifecycleTests { } @Test + @SkipPresubmit("b/180015146") public void testNoCpuDataForRemovedUser() throws Exception { mIam.startUserInBackground(mTestUserId); waitUntilTrue("No uids for started user " + mTestUserId, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java new file mode 100644 index 000000000000..c4b7796b49cf --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Process; +import android.os.UidBatteryConsumer; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BatteryUsageStatsProviderTest { + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final long MINUTE_IN_MS = 60 * 1000; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + + @Test + public void test_getBatteryUsageStats() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteActivityResumedLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteActivityPausedLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_SERVICE, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY, + 40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS); + + Context context = InstrumentationRegistry.getContext(); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + + final BatteryUsageStats batteryUsageStats = + provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT); + + final List<UidBatteryConsumer> uidBatteryConsumers = + batteryUsageStats.getUidBatteryConsumers(); + final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0); + assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND)) + .isEqualTo(20 * MINUTE_IN_MS); + assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND)) + .isEqualTo(10 * MINUTE_IN_MS); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 355ac6dbcc00..23ea508d19d3 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import java.util.List; @SmallTest +@SkipPresubmit("b/180015146") @RunWith(AndroidJUnit4.class) public class BatteryUsageStatsTest { @@ -66,33 +67,36 @@ public class BatteryUsageStatsTest { final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000); - final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1); - builder.setDischargePercentage(20); - builder.setDischargedPowerRange(1000, 2000); - - final UidBatteryConsumer.Builder uidBatteryConsumerBuilder = - builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid); - uidBatteryConsumerBuilder.setPackageWithHighestDrain("foo"); - uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, 300); - uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400); - uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500); - uidBatteryConsumerBuilder.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, 600); - uidBatteryConsumerBuilder.setUsageDurationMillis( - BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700); - uidBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 800); - - final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = - builder.getOrCreateSystemBatteryConsumerBuilder( - SystemBatteryConsumer.DRAIN_TYPE_CAMERA); - systemBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100); - systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200); - systemBatteryConsumerBuilder.setUsageDurationMillis( - BatteryConsumer.TIME_COMPONENT_CPU, 10300); - systemBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 10400); + final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1) + .setDischargePercentage(20) + .setDischargedPowerRange(1000, 2000); + + builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) + .setPackageWithHighestDrain("foo") + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_USAGE, 300) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 400) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU, 600) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 800); + + builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_CAMERA) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU, 10300) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 10400); return builder.build(); } @@ -108,6 +112,10 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == 2000) { assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo"); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(1000); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000); assertThat(uidBatteryConsumer.getConsumedPower( BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300); assertThat(uidBatteryConsumer.getConsumedPower( diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index e5594712db10..f6aa08bf0645 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -43,6 +43,7 @@ public class BluetoothPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0); @Test + @SkipPresubmit("b/180015146") public void testTimerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), @@ -73,6 +74,7 @@ public class BluetoothPowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testReportedPowerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java index a80f5a03ee4e..4fe7d70e86ff 100644 --- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java @@ -382,6 +382,7 @@ public class BstatsCpuTimesValidationTest { } @Test + @SkipPresubmit("b/180015146 flakey") public void testCpuFreqTimes_stateFgService() throws Exception { if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) { Log.w(TAG, "Skipping " + testName.getMethodName() @@ -514,6 +515,7 @@ public class BstatsCpuTimesValidationTest { } @Test + @SkipPresubmit("b/180015146") public void testCpuFreqTimes_trackingDisabled() throws Exception { if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) { Log.w(TAG, "Skipping " + testName.getMethodName() diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java index 9cf0d375ff51..e691beb09a70 100644 --- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java @@ -92,6 +92,7 @@ public class CpuPowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testTimerBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java index a4ea8923794a..f298f5988fc3 100644 --- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java @@ -42,6 +42,7 @@ public class CustomMeasuredPowerCalculatorTest { public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); @Test + @SkipPresubmit("b/180015146") public void testMeasuredEnergyCopiedIntoBatteryConsumers() { final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); SparseLongArray uidEnergies = new SparseLongArray(); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java index 7dca0cb92f9d..177f34875894 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java @@ -87,6 +87,7 @@ public class KernelCpuUidUserSysTimeReaderTest { } @Test + @SkipPresubmit("b/180015146") public void testThrottler() throws Exception { mReader = new KernelCpuUidUserSysTimeReader( new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), true); diff --git a/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java new file mode 100644 index 000000000000..d03ed663cc89 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation to skip a test from TEST_MAPPING presubmit. */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface SkipPresubmit { + /** The optional reason why the test is ignored. */ + String value() default ""; +} diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java index dfbf28b286c6..b5282e9a625a 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -78,6 +78,7 @@ public class SystemServicePowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testPowerProfileBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java new file mode 100644 index 000000000000..e1005457c289 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + + +import static com.google.common.truth.Truth.assertThat; + +import android.net.NetworkCapabilities; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; +import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WifiPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ON, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0); + + @Test + public void testPowerControllerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + WifiActivityEnergyInfo energyInfo = new WifiActivityEnergyInfo(10000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + + batteryStats.updateWifiState(energyInfo, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.2214666); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(5577); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.645200); + } + + @Test + public void testTimerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + batteryStats.noteWifiScanStartedLocked(APP_UID, 1000, 1000); + batteryStats.noteWifiScanStoppedLocked(APP_UID, 2000, 2000); + batteryStats.noteWifiRunningLocked(new WorkSource(APP_UID), 3000, 3000); + batteryStats.noteWifiStoppedLocked(new WorkSource(APP_UID), 4000, 4000); + batteryStats.noteWifiRunningLocked(new WorkSource(Process.WIFI_UID), 1111, 2222); + batteryStats.noteWifiStoppedLocked(new WorkSource(Process.WIFI_UID), 3333, 4444); + + // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively + // on the packet counts. + batteryStats.updateWifiState(/* energyInfo */ null, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1000); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8231573); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(2222); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8759216); + } +} diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java index 5fd5a7838c3a..d217bce24b9c 100644 --- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java @@ -81,11 +81,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats); @@ -114,11 +114,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); stats.writeToParcel(parcel); @@ -149,11 +149,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -185,17 +185,17 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true); - stats.updateCustomBucket(0, 315, true); - stats.updateCustomBucket(1, 316, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63); + stats.updateCustomBucket(0, 315); + stats.updateCustomBucket(1, 316); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -243,8 +243,8 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); // Accumulate energy in one bucket and one custom bucket, the rest should be zero - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateCustomBucket(1, 60); // Let's try parcelling with including zeros final Parcel includeZerosParcel = Parcel.obtain(); @@ -305,11 +305,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -331,14 +331,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -369,14 +369,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(0, 3, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(0, 3); assertEquals(15, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); assertEquals(ENERGY_DATA_UNAVAILABLE, @@ -409,10 +409,10 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(2, 13, true); - stats.updateCustomBucket(1, 70, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(2, 13); + stats.updateCustomBucket(1, 70); final long[] output = stats.getAccumulatedCustomBucketEnergies(); assertEquals(3, output.length); @@ -449,11 +449,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); MeasuredEnergyStats.resetIfNotNull(stats); // All energy should be reset to 0 @@ -471,10 +471,10 @@ public class MeasuredEnergyStatsTest { } // Values should increase as usual. - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70); assertEquals(70L, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); - stats.updateCustomBucket(1, 12, true); + stats.updateCustomBucket(1, 12); assertEquals(12L, stats.getAccumulatedCustomBucketEnergy(1)); } diff --git a/core/tests/coretests/src/com/android/internal/view/OWNERS b/core/tests/coretests/src/com/android/internal/view/OWNERS new file mode 100644 index 000000000000..1dad10de5ac7 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/view/OWNERS @@ -0,0 +1,3 @@ +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp index 409b77bc399e..f7b593264cda 100644 --- a/core/tests/devicestatetests/Android.bp +++ b/core/tests/devicestatetests/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "FrameworksCoreDeviceStateManagerTests", // Include all test java files diff --git a/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp index 42421ce0e9f3..3536c4088dd8 100644 --- a/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp @@ -50,4 +50,5 @@ apex { key: "com.android.overlaytest.overlaid.key", apps: ["OverlayRemountedTest_Target"], installable: false, + updatable: false, } diff --git a/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp index 0b52dcc4fb85..f04140409bea 100644 --- a/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp @@ -50,4 +50,5 @@ apex { key: "com.android.overlaytest.overlay.key", apps: ["OverlayRemountedTest_Overlay"], installable: false, + updatable: false, } diff --git a/data/etc/car/com.google.android.car.networking.preferenceupdater.xml b/data/etc/car/com.google.android.car.networking.preferenceupdater.xml index 489ce1b47ffa..cdeb8e48a59b 100644 --- a/data/etc/car/com.google.android.car.networking.preferenceupdater.xml +++ b/data/etc/car/com.google.android.car.networking.preferenceupdater.xml @@ -16,12 +16,21 @@ --> <permissions> <privapp-permissions package="com.google.android.car.networking.preferenceupdater"> - <permission name="android.permission.ACCESS_NETWORK_STATE"/> + <permission name="android.permission.ACCESS_NETWORK_STATE" /> <permission name="android.permission.ACCESS_WIFI_STATE"/> <permission name="android.permission.ACTIVITY_EMBEDDING"/> + <permission name="android.permission.CHANGE_NETWORK_STATE" /> + <permission name="android.permission.CONNECTIVITY_INTERNAL" /> + <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> + <permission name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERNCE" /> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/> - <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> + <permission name="android.permission.INTERNET" /> <permission name="android.permission.LOCATION_HARDWARE"/> + <permission name="android.permission.PACKAGE_USAGE_STATS" /> + <permission name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <permission name="android.permission.WAKE_LOCK" /> + <permission name="android.permission.WRITE_SETTINGS" /> + <permission name="android.car.permission.CAR_DRIVING_STATE" /> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8fd5d804adc0..3900d7e674ca 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -406,8 +406,6 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> - <!-- Permissions required for Incremental CTS tests --> - <permission name="com.android.permission.USE_INSTALLER_V2"/> <permission name="android.permission.LOADER_USAGE_STATS"/> <!-- Permission required to test system only camera devices. --> <permission name="android.permission.SYSTEM_CAMERA" /> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 2db4c5d6bf2a..4f188cc03282 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -223,7 +223,7 @@ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" /> <family name="serif"> - <font weight="400" style="normal">NotoSerif-Regular.ttf</font> + <font weight="400" style="normal">NotoSerif.ttf</font> <font weight="700" style="normal">NotoSerif-Bold.ttf</font> <font weight="400" style="italic">NotoSerif-Italic.ttf</font> <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font> @@ -275,144 +275,144 @@ <!-- fallback fonts --> <family lang="und-Arab" variant="elegant"> - <font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font> + <font weight="400" style="normal">NotoNaskhArabic.ttf</font> <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font> </family> <family lang="und-Arab" variant="compact"> - <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font> + <font weight="400" style="normal">NotoNaskhArabicUI.ttf</font> <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> </family> <family lang="und-Ethi"> - <font weight="400" style="normal">NotoSansEthiopic-VF.ttf + <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansEthiopic-VF.ttf + <font weight="500" style="normal">NotoSansEthiopic-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansEthiopic-VF.ttf + <font weight="600" style="normal">NotoSansEthiopic-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansEthiopic-VF.ttf + <font weight="700" style="normal">NotoSansEthiopic-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Hebr"> - <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font> + <font weight="400" style="normal">NotoSansHebrew.ttf</font> <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font> <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font> <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font> </family> <family lang="und-Thai" variant="elegant"> - <font weight="400" style="normal">NotoSansThai-Regular.ttf</font> + <font weight="400" style="normal">NotoSansThai.ttf</font> <font weight="700" style="normal">NotoSansThai-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifThai-Regular.ttf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifThai.ttf</font> <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font> </family> <family lang="und-Thai" variant="compact"> - <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font> + <font weight="400" style="normal">NotoSansThaiUI.ttf</font> <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font> </family> <family lang="und-Armn"> - <font weight="400" style="normal">NotoSansArmenian-VF.ttf + <font weight="400" style="normal">NotoSansArmenian-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansArmenian-VF.ttf + <font weight="500" style="normal">NotoSansArmenian-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansArmenian-VF.ttf + <font weight="600" style="normal">NotoSansArmenian-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansArmenian-VF.ttf + <font weight="700" style="normal">NotoSansArmenian-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Geor,und-Geok"> - <font weight="400" style="normal">NotoSansGeorgian-VF.ttf + <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansGeorgian-VF.ttf + <font weight="500" style="normal">NotoSansGeorgian-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansGeorgian-VF.ttf + <font weight="600" style="normal">NotoSansGeorgian-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansGeorgian-VF.ttf + <font weight="700" style="normal">NotoSansGeorgian-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifGeorgian-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifGeorgian-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifGeorgian-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifGeorgian-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Deva" variant="elegant"> - <font weight="400" style="normal">NotoSansDevanagari-VF.ttf + <font weight="400" style="normal">NotoSansDevanagari-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansDevanagari-VF.ttf + <font weight="500" style="normal">NotoSansDevanagari-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansDevanagari-VF.ttf + <font weight="600" style="normal">NotoSansDevanagari-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansDevanagari-VF.ttf + <font weight="700" style="normal">NotoSansDevanagari-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifDevanagari-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifDevanagari-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifDevanagari-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifDevanagari-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Deva" variant="compact"> - <font weight="400" style="normal">NotoSansDevanagariUI-VF.ttf + <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansDevanagariUI-VF.ttf + <font weight="500" style="normal">NotoSansDevanagariUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansDevanagariUI-VF.ttf + <font weight="600" style="normal">NotoSansDevanagariUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansDevanagariUI-VF.ttf + <font weight="700" style="normal">NotoSansDevanagariUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> @@ -421,347 +421,347 @@ danda characters. --> <family lang="und-Gujr" variant="elegant"> - <font weight="400" style="normal">NotoSansGujarati-Regular.ttf</font> + <font weight="400" style="normal">NotoSansGujarati.ttf</font> <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifGujarati-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifGujarati-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifGujarati-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifGujarati-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Gujr" variant="compact"> - <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font> + <font weight="400" style="normal">NotoSansGujaratiUI.ttf</font> <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font> </family> <family lang="und-Guru" variant="elegant"> - <font weight="400" style="normal">NotoSansGurmukhi-VF.ttf + <font weight="400" style="normal">NotoSansGurmukhi-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansGurmukhi-VF.ttf + <font weight="500" style="normal">NotoSansGurmukhi-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansGurmukhi-VF.ttf + <font weight="600" style="normal">NotoSansGurmukhi-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansGurmukhi-VF.ttf + <font weight="700" style="normal">NotoSansGurmukhi-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifGurmukhi-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifGurmukhi-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifGurmukhi-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifGurmukhi-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Guru" variant="compact"> - <font weight="400" style="normal">NotoSansGurmukhiUI-VF.ttf + <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansGurmukhiUI-VF.ttf + <font weight="500" style="normal">NotoSansGurmukhiUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansGurmukhiUI-VF.ttf + <font weight="600" style="normal">NotoSansGurmukhiUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansGurmukhiUI-VF.ttf + <font weight="700" style="normal">NotoSansGurmukhiUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Taml" variant="elegant"> - <font weight="400" style="normal">NotoSansTamil-VF.ttf + <font weight="400" style="normal">NotoSansTamil-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansTamil-VF.ttf + <font weight="500" style="normal">NotoSansTamil-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansTamil-VF.ttf + <font weight="600" style="normal">NotoSansTamil-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansTamil-VF.ttf + <font weight="700" style="normal">NotoSansTamil-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifTamil-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifTamil-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifTamil-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifTamil-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Taml" variant="compact"> - <font weight="400" style="normal">NotoSansTamilUI-VF.ttf + <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansTamilUI-VF.ttf + <font weight="500" style="normal">NotoSansTamilUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansTamilUI-VF.ttf + <font weight="600" style="normal">NotoSansTamilUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansTamilUI-VF.ttf + <font weight="700" style="normal">NotoSansTamilUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Mlym" variant="elegant"> - <font weight="400" style="normal">NotoSansMalayalam-VF.ttf + <font weight="400" style="normal">NotoSansMalayalam-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansMalayalam-VF.ttf + <font weight="500" style="normal">NotoSansMalayalam-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansMalayalam-VF.ttf + <font weight="600" style="normal">NotoSansMalayalam-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansMalayalam-VF.ttf + <font weight="700" style="normal">NotoSansMalayalam-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifMalayalam-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifMalayalam-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifMalayalam-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifMalayalam-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Mlym" variant="compact"> - <font weight="400" style="normal">NotoSansMalayalamUI-VF.ttf + <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansMalayalamUI-VF.ttf + <font weight="500" style="normal">NotoSansMalayalamUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansMalayalamUI-VF.ttf + <font weight="600" style="normal">NotoSansMalayalamUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansMalayalamUI-VF.ttf + <font weight="700" style="normal">NotoSansMalayalamUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Beng" variant="elegant"> - <font weight="400" style="normal">NotoSansBengali-VF.ttf + <font weight="400" style="normal">NotoSansBengali-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansBengali-VF.ttf + <font weight="500" style="normal">NotoSansBengali-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansBengali-VF.ttf + <font weight="600" style="normal">NotoSansBengali-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansBengali-VF.ttf + <font weight="700" style="normal">NotoSansBengali-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifBengali-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifBengali-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifBengali-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifBengali-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Beng" variant="compact"> - <font weight="400" style="normal">NotoSansBengaliUI-VF.ttf + <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansBengaliUI-VF.ttf + <font weight="500" style="normal">NotoSansBengaliUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansBengaliUI-VF.ttf + <font weight="600" style="normal">NotoSansBengaliUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansBengaliUI-VF.ttf + <font weight="700" style="normal">NotoSansBengaliUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Telu" variant="elegant"> - <font weight="400" style="normal">NotoSansTelugu-VF.ttf + <font weight="400" style="normal">NotoSansTelugu-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansTelugu-VF.ttf + <font weight="500" style="normal">NotoSansTelugu-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansTelugu-VF.ttf + <font weight="600" style="normal">NotoSansTelugu-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansTelugu-VF.ttf + <font weight="700" style="normal">NotoSansTelugu-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifTelugu-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifTelugu-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifTelugu-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifTelugu-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Telu" variant="compact"> - <font weight="400" style="normal">NotoSansTeluguUI-VF.ttf + <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansTeluguUI-VF.ttf + <font weight="500" style="normal">NotoSansTeluguUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansTeluguUI-VF.ttf + <font weight="600" style="normal">NotoSansTeluguUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansTeluguUI-VF.ttf + <font weight="700" style="normal">NotoSansTeluguUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Knda" variant="elegant"> - <font weight="400" style="normal">NotoSansKannada-VF.ttf + <font weight="400" style="normal">NotoSansKannada-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansKannada-VF.ttf + <font weight="500" style="normal">NotoSansKannada-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansKannada-VF.ttf + <font weight="600" style="normal">NotoSansKannada-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansKannada-VF.ttf + <font weight="700" style="normal">NotoSansKannada-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifKannada-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifKannada-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifKannada-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifKannada-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Knda" variant="compact"> - <font weight="400" style="normal">NotoSansKannadaUI-VF.ttf + <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansKannadaUI-VF.ttf + <font weight="500" style="normal">NotoSansKannadaUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansKannadaUI-VF.ttf + <font weight="600" style="normal">NotoSansKannadaUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansKannadaUI-VF.ttf + <font weight="700" style="normal">NotoSansKannadaUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Orya" variant="elegant"> - <font weight="400" style="normal">NotoSansOriya-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOriya.ttf</font> <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font> </family> <family lang="und-Orya" variant="compact"> - <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOriyaUI.ttf</font> <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font> </family> <family lang="und-Sinh" variant="elegant"> - <font weight="400" style="normal">NotoSansSinhala-VF.ttf + <font weight="400" style="normal">NotoSansSinhala-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansSinhala-VF.ttf + <font weight="500" style="normal">NotoSansSinhala-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansSinhala-VF.ttf + <font weight="600" style="normal">NotoSansSinhala-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansSinhala-VF.ttf + <font weight="700" style="normal">NotoSansSinhala-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifSinhala-VF.ttf + <font weight="400" style="normal" fallbackFor="serif">NotoSerifSinhala-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" fallbackFor="serif">NotoSerifSinhala-VF.ttf + <font weight="500" style="normal" fallbackFor="serif">NotoSerifSinhala-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif">NotoSerifSinhala-VF.ttf + <font weight="600" style="normal" fallbackFor="serif">NotoSerifSinhala-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif">NotoSerifSinhala-VF.ttf + <font weight="700" style="normal" fallbackFor="serif">NotoSerifSinhala-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Sinh" variant="compact"> - <font weight="400" style="normal">NotoSansSinhalaUI-VF.ttf + <font weight="400" style="normal">NotoSansSinhalaUI-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansSinhalaUI-VF.ttf + <font weight="500" style="normal">NotoSansSinhalaUI-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansSinhalaUI-VF.ttf + <font weight="600" style="normal">NotoSansSinhalaUI-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansSinhalaUI-VF.ttf + <font weight="700" style="normal">NotoSansSinhalaUI-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Khmr" variant="elegant"> - <font weight="100" style="normal">NotoSansKhmer-VF.ttf + <font weight="100" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="26.0"/> </font> - <font weight="200" style="normal">NotoSansKhmer-VF.ttf + <font weight="200" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="39.0"/> </font> - <font weight="300" style="normal">NotoSansKhmer-VF.ttf + <font weight="300" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="58.0"/> </font> - <font weight="400" style="normal">NotoSansKhmer-VF.ttf + <font weight="400" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="90.0"/> </font> - <font weight="500" style="normal">NotoSansKhmer-VF.ttf + <font weight="500" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="108.0"/> </font> - <font weight="600" style="normal">NotoSansKhmer-VF.ttf + <font weight="600" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="128.0"/> </font> - <font weight="700" style="normal">NotoSansKhmer-VF.ttf + <font weight="700" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="151.0"/> </font> - <font weight="800" style="normal">NotoSansKhmer-VF.ttf + <font weight="800" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="169.0"/> </font> - <font weight="900" style="normal">NotoSansKhmer-VF.ttf + <font weight="900" style="normal">NotoSansKhmer-Regular.ttf <axis tag="wdth" stylevalue="100.0"/> <axis tag="wght" stylevalue="190.0"/> </font> @@ -769,17 +769,17 @@ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font> </family> <family lang="und-Khmr" variant="compact"> - <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font> + <font weight="400" style="normal">NotoSansKhmerUI.ttf</font> <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font> </family> <family lang="und-Laoo" variant="elegant"> - <font weight="400" style="normal">NotoSansLao-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLao.ttf</font> <font weight="700" style="normal">NotoSansLao-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif">NotoSerifLao-Regular.ttf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifLao.ttf</font> <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font> </family> <family lang="und-Laoo" variant="compact"> - <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLaoUI.ttf</font> <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> </family> <family lang="und-Mymr" variant="elegant"> @@ -795,56 +795,56 @@ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font> </family> <family lang="und-Thaa"> - <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font> + <font weight="400" style="normal">NotoSansThaana.ttf</font> <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font> </family> <family lang="und-Cham"> - <font weight="400" style="normal">NotoSansCham-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCham.ttf</font> <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> </family> <family lang="und-Ahom"> <font weight="400" style="normal">NotoSansAhom-Regular.otf</font> </family> <family lang="und-Adlm"> - <font weight="400" style="normal">NotoSansAdlam-VF.ttf + <font weight="400" style="normal">NotoSansAdlam-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansAdlam-VF.ttf + <font weight="500" style="normal">NotoSansAdlam-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansAdlam-VF.ttf + <font weight="600" style="normal">NotoSansAdlam-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansAdlam-VF.ttf + <font weight="700" style="normal">NotoSansAdlam-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Avst"> - <font weight="400" style="normal">NotoSansAvestan-Regular.ttf</font> + <font weight="400" style="normal">NotoSansAvestan.ttf</font> </family> <family lang="und-Bali"> - <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBalinese.ttf</font> </family> <family lang="und-Bamu"> - <font weight="400" style="normal">NotoSansBamum-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBamum.ttf</font> </family> <family lang="und-Batk"> - <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBatak.ttf</font> </family> <family lang="und-Brah"> - <font weight="400" style="normal">NotoSansBrahmi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBrahmi.ttf</font> </family> <family lang="und-Bugi"> - <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBuginese.ttf</font> </family> <family lang="und-Buhd"> - <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font> + <font weight="400" style="normal">NotoSansBuhid.ttf</font> </family> <family lang="und-Cans"> - <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCanadianAboriginal.ttf</font> </family> <family lang="und-Cari"> - <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCarian.ttf</font> </family> <family lang="und-Cakm"> <font weight="400" style="normal">NotoSansChakma-Regular.otf</font> @@ -853,164 +853,164 @@ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> </family> <family lang="und-Copt"> - <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCoptic.ttf</font> </family> <family lang="und-Xsux"> - <font weight="400" style="normal">NotoSansCuneiform-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCuneiform.ttf</font> </family> <family lang="und-Cprt"> - <font weight="400" style="normal">NotoSansCypriot-Regular.ttf</font> + <font weight="400" style="normal">NotoSansCypriot.ttf</font> </family> <family lang="und-Dsrt"> - <font weight="400" style="normal">NotoSansDeseret-Regular.ttf</font> + <font weight="400" style="normal">NotoSansDeseret.ttf</font> </family> <family lang="und-Egyp"> - <font weight="400" style="normal">NotoSansEgyptianHieroglyphs-Regular.ttf</font> + <font weight="400" style="normal">NotoSansEgyptianHieroglyphs.ttf</font> </family> <family lang="und-Elba"> <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font> </family> <family lang="und-Glag"> - <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansGlagolitic.ttf</font> </family> <family lang="und-Goth"> - <font weight="400" style="normal">NotoSansGothic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansGothic.ttf</font> </family> <family lang="und-Hano"> - <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font> + <font weight="400" style="normal">NotoSansHanunoo.ttf</font> </family> <family lang="und-Armi"> - <font weight="400" style="normal">NotoSansImperialAramaic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansImperialAramaic.ttf</font> </family> <family lang="und-Phli"> - <font weight="400" style="normal">NotoSansInscriptionalPahlavi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansInscriptionalPahlavi.ttf</font> </family> <family lang="und-Prti"> - <font weight="400" style="normal">NotoSansInscriptionalParthian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansInscriptionalParthian.ttf</font> </family> <family lang="und-Java"> <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font> </family> <family lang="und-Kthi"> - <font weight="400" style="normal">NotoSansKaithi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansKaithi.ttf</font> </family> <family lang="und-Kali"> - <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansKayahLi.ttf</font> </family> <family lang="und-Khar"> - <font weight="400" style="normal">NotoSansKharoshthi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansKharoshthi.ttf</font> </family> <family lang="und-Lepc"> - <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLepcha.ttf</font> </family> <family lang="und-Limb"> - <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLimbu.ttf</font> </family> <family lang="und-Linb"> - <font weight="400" style="normal">NotoSansLinearB-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLinearB.ttf</font> </family> <family lang="und-Lisu"> - <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLisu.ttf</font> </family> <family lang="und-Lyci"> - <font weight="400" style="normal">NotoSansLycian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLycian.ttf</font> </family> <family lang="und-Lydi"> - <font weight="400" style="normal">NotoSansLydian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansLydian.ttf</font> </family> <family lang="und-Mand"> - <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansMandaic.ttf</font> </family> <family lang="und-Mtei"> - <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font> + <font weight="400" style="normal">NotoSansMeeteiMayek.ttf</font> </family> <family lang="und-Talu"> - <font weight="400" style="normal">NotoSansNewTaiLue-Regular.ttf</font> + <font weight="400" style="normal">NotoSansNewTaiLue.ttf</font> </family> <family lang="und-Nkoo"> - <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font> + <font weight="400" style="normal">NotoSansNKo.ttf</font> </family> <family lang="und-Ogam"> - <font weight="400" style="normal">NotoSansOgham-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOgham.ttf</font> </family> <family lang="und-Olck"> - <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOlChiki.ttf</font> </family> <family lang="und-Ital"> - <font weight="400" style="normal">NotoSansOldItalic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOldItalic.ttf</font> </family> <family lang="und-Xpeo"> - <font weight="400" style="normal">NotoSansOldPersian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOldPersian.ttf</font> </family> <family lang="und-Sarb"> - <font weight="400" style="normal">NotoSansOldSouthArabian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOldSouthArabian.ttf</font> </family> <family lang="und-Orkh"> - <font weight="400" style="normal">NotoSansOldTurkic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOldTurkic.ttf</font> </family> <family lang="und-Osge"> <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font> </family> <family lang="und-Osma"> - <font weight="400" style="normal">NotoSansOsmanya-Regular.ttf</font> + <font weight="400" style="normal">NotoSansOsmanya.ttf</font> </family> <family lang="und-Phnx"> - <font weight="400" style="normal">NotoSansPhoenician-Regular.ttf</font> + <font weight="400" style="normal">NotoSansPhoenician.ttf</font> </family> <family lang="und-Rjng"> - <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font> + <font weight="400" style="normal">NotoSansRejang.ttf</font> </family> <family lang="und-Runr"> - <font weight="400" style="normal">NotoSansRunic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansRunic.ttf</font> </family> <family lang="und-Samr"> - <font weight="400" style="normal">NotoSansSamaritan-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSamaritan.ttf</font> </family> <family lang="und-Saur"> - <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSaurashtra.ttf</font> </family> <family lang="und-Shaw"> - <font weight="400" style="normal">NotoSansShavian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansShavian.ttf</font> </family> <family lang="und-Sund"> - <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSundanese.ttf</font> </family> <family lang="und-Sylo"> - <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSylotiNagri.ttf</font> </family> <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. --> <family lang="und-Syre"> - <font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSyriacEstrangela.ttf</font> </family> <family lang="und-Syrn"> - <font weight="400" style="normal">NotoSansSyriacEastern-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSyriacEastern.ttf</font> </family> <family lang="und-Syrj"> - <font weight="400" style="normal">NotoSansSyriacWestern-Regular.ttf</font> + <font weight="400" style="normal">NotoSansSyriacWestern.ttf</font> </family> <family lang="und-Tglg"> - <font weight="400" style="normal">NotoSansTagalog-Regular.ttf</font> + <font weight="400" style="normal">NotoSansTagalog.ttf</font> </family> <family lang="und-Tagb"> - <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font> + <font weight="400" style="normal">NotoSansTagbanwa.ttf</font> </family> <family lang="und-Lana"> - <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font> + <font weight="400" style="normal">NotoSansTaiTham.ttf</font> </family> <family lang="und-Tavt"> - <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font> + <font weight="400" style="normal">NotoSansTaiViet.ttf</font> </family> <family lang="und-Tibt"> - <font weight="400" style="normal">NotoSerifTibetan-VF.ttf + <font weight="400" style="normal">NotoSerifTibetan-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSerifTibetan-VF.ttf + <font weight="500" style="normal">NotoSerifTibetan-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSerifTibetan-VF.ttf + <font weight="600" style="normal">NotoSerifTibetan-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSerifTibetan-VF.ttf + <font weight="700" style="normal">NotoSerifTibetan-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> @@ -1018,29 +1018,29 @@ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font> </family> <family lang="und-Ugar"> - <font weight="400" style="normal">NotoSansUgaritic-Regular.ttf</font> + <font weight="400" style="normal">NotoSansUgaritic.ttf</font> </family> <family lang="und-Vaii"> - <font weight="400" style="normal">NotoSansVai-Regular.ttf</font> + <font weight="400" style="normal">NotoSansVai.ttf</font> </family> <family> <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family lang="zh-Hans"> - <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font> - <font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font> + <font weight="400" style="normal" index="2">NotoSansCJKjp-Regular.otc</font> + <font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJKjp-Regular.otc</font> </family> <family lang="zh-Hant,zh-Bopo"> - <font weight="400" style="normal" index="3">NotoSansCJK-Regular.ttc</font> - <font weight="400" style="normal" index="3" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font> + <font weight="400" style="normal" index="3">NotoSansCJKjp-Regular.otc</font> + <font weight="400" style="normal" index="3" fallbackFor="serif">NotoSerifCJKjp-Regular.otc</font> </family> <family lang="ja"> - <font weight="400" style="normal" index="0">NotoSansCJK-Regular.ttc</font> - <font weight="400" style="normal" index="0" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font> + <font weight="400" style="normal" index="0">NotoSansCJKjp-Regular.otc</font> + <font weight="400" style="normal" index="0" fallbackFor="serif">NotoSerifCJKjp-Regular.otc</font> </family> <family lang="ko"> - <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font> - <font weight="400" style="normal" index="1" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font> + <font weight="400" style="normal" index="1">NotoSansCJKjp-Regular.otc</font> + <font weight="400" style="normal" index="1" fallbackFor="serif">NotoSerifCJKjp-Regular.otc</font> </family> <family lang="und-Zsye"> <font weight="400" style="normal">NotoColorEmoji.ttf</font> @@ -1053,16 +1053,16 @@ override the East Asian punctuation for Chinese. --> <family lang="und-Tale"> - <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font> + <font weight="400" style="normal">NotoSansTaiLe.ttf</font> </family> <family lang="und-Yiii"> - <font weight="400" style="normal">NotoSansYi-Regular.ttf</font> + <font weight="400" style="normal">NotoSansYi.ttf</font> </family> <family lang="und-Mong"> - <font weight="400" style="normal">NotoSansMongolian-Regular.ttf</font> + <font weight="400" style="normal">NotoSansMongolian.ttf</font> </family> <family lang="und-Phag"> - <font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font> + <font weight="400" style="normal">NotoSansPhagsPa.ttf</font> </family> <family lang="und-Hluw"> <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font> @@ -1152,72 +1152,72 @@ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font> </family> <family lang="und-Medf"> - <font weight="400" style="normal">NotoSansMedefaidrin-VF.ttf + <font weight="400" style="normal">NotoSansMedefaidrin-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansMedefaidrin-VF.ttf + <font weight="500" style="normal">NotoSansMedefaidrin-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansMedefaidrin-VF.ttf + <font weight="600" style="normal">NotoSansMedefaidrin-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansMedefaidrin-VF.ttf + <font weight="700" style="normal">NotoSansMedefaidrin-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Soyo"> - <font weight="400" style="normal">NotoSansSoyombo-VF.ttf + <font weight="400" style="normal">NotoSansSoyombo-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansSoyombo-VF.ttf + <font weight="500" style="normal">NotoSansSoyombo-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansSoyombo-VF.ttf + <font weight="600" style="normal">NotoSansSoyombo-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansSoyombo-VF.ttf + <font weight="700" style="normal">NotoSansSoyombo-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Takr"> - <font weight="400" style="normal">NotoSansTakri-VF.ttf + <font weight="400" style="normal">NotoSansTakri-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSansTakri-VF.ttf + <font weight="500" style="normal">NotoSansTakri-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSansTakri-VF.ttf + <font weight="600" style="normal">NotoSansTakri-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSansTakri-VF.ttf + <font weight="700" style="normal">NotoSansTakri-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Hmnp"> - <font weight="400" style="normal">NotoSerifNyiakengPuachueHmong-VF.ttf + <font weight="400" style="normal">NotoSerifHmongNyiakeng-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSerifNyiakengPuachueHmong-VF.ttf + <font weight="500" style="normal">NotoSerifHmongNyiakeng-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSerifNyiakengPuachueHmong-VF.ttf + <font weight="600" style="normal">NotoSerifHmongNyiakeng-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSerifNyiakengPuachueHmong-VF.ttf + <font weight="700" style="normal">NotoSerifHmongNyiakeng-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Yezi"> - <font weight="400" style="normal">NotoSerifYezidi-VF.ttf + <font weight="400" style="normal">NotoSerifYezidi-Regular.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal">NotoSerifYezidi-VF.ttf + <font weight="500" style="normal">NotoSerifYezidi-Regular.ttf <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal">NotoSerifYezidi-VF.ttf + <font weight="600" style="normal">NotoSerifYezidi-Regular.ttf <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal">NotoSerifYezidi-VF.ttf + <font weight="700" style="normal">NotoSerifYezidi-Regular.ttf <axis tag="wght" stylevalue="700"/> </font> </family> diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS index c4f6df824a39..0ce83507160c 100644 --- a/data/keyboards/OWNERS +++ b/data/keyboards/OWNERS @@ -1,5 +1,3 @@ set noparent -michaelwr@google.com -svv@google.com -lzye@google.com +include /services/core/java/com/android/server/input/OWNERS diff --git a/data/keyboards/Vendor_057e_Product_2009.kl b/data/keyboards/Vendor_057e_Product_2009.kl index 3c6b11e4640c..7491ee562b59 100644 --- a/data/keyboards/Vendor_057e_Product_2009.kl +++ b/data/keyboards/Vendor_057e_Product_2009.kl @@ -74,3 +74,11 @@ key 0x135 BUTTON_MODE # Home key key 0x13c HOME + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java index d59abb5916a0..189be53a397f 100644 --- a/graphics/java/android/graphics/FrameInfo.java +++ b/graphics/java/android/graphics/FrameInfo.java @@ -69,28 +69,26 @@ public final class FrameInfo { // animation & drawing system public static final int VSYNC = 3; - // The time of the oldest input event - public static final int OLDEST_INPUT_EVENT = 4; - - // The time of the newest input event - public static final int NEWEST_INPUT_EVENT = 5; + // The id of the input event that caused the current frame + public static final int INPUT_EVENT_ID = 4; // When input event handling started - public static final int HANDLE_INPUT_START = 6; + public static final int HANDLE_INPUT_START = 5; // When animation evaluations started - public static final int ANIMATION_START = 7; + public static final int ANIMATION_START = 6; // When ViewRootImpl#performTraversals() started - public static final int PERFORM_TRAVERSALS_START = 8; + public static final int PERFORM_TRAVERSALS_START = 7; // When View:draw() started - public static final int DRAW_START = 9; + public static final int DRAW_START = 8; // When the frame needs to be ready by - public static final int FRAME_DEADLINE = 10; + public static final int FRAME_DEADLINE = 9; // Must be the last one + // This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1; /** checkstyle */ diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index f1f9a5fc92ea..e22257071dd2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -153,6 +153,13 @@ public class Paint { * resource bitmaps often are) the filtering will already have been * done.</p> * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always uses bilinear sampling on scaled bitmaps, + * regardless of this flag. On devices running {@link Build.VERSION_CODES#Q} + * and above, this flag defaults to being set on a new {@code Paint}. It can + * be cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p> + * + * @see #Paint() * @see #Paint(int) * @see #setFlags(int) */ @@ -558,6 +565,12 @@ public class Paint { /** * Create a new paint with default settings. + * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set. + * On devices running {@link Build.VERSION_CODES#Q} and above, + * {@code FILTER_BITMAP_FLAG} is set by this constructor, and it can be + * cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p> */ public Paint() { this(0); @@ -567,6 +580,13 @@ public class Paint { * Create a new paint with the specified flags. Use setFlags() to change * these after the paint is created. * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set. + * On devices running {@link Build.VERSION_CODES#Q} and above, + * {@code FILTER_BITMAP_FLAG} is always set by this constructor, regardless + * of the value of {@code flags}. It can be cleared with {@link #setFlags} or + * {@link #setFilterBitmap}.</p> + * * @param flags initial flag bits, as if they were passed via setFlags(). */ public Paint(int flags) { @@ -991,6 +1011,7 @@ public class Paint { * device pixels. That is dependent on dithering and xfermodes. * * @see #setFilterBitmap(boolean) setFilterBitmap() + * @see #FILTER_BITMAP_FLAG */ public final boolean isFilterBitmap() { return (getFlags() & FILTER_BITMAP_FLAG) != 0; @@ -1004,6 +1025,7 @@ public class Paint { * * @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's * flags, false to clear it. + * @see #FILTER_BITMAP_FLAG */ public void setFilterBitmap(boolean filter) { nSetFilterBitmap(mNativePaint, filter); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 32c777cf498c..c80788269c24 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -172,9 +172,9 @@ public class Typeface { * @hide */ @UnsupportedAppUsage - public long native_instance; + public final long native_instance; - private Runnable mCleaner; + private final Runnable mCleaner; /** @hide */ @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC}) @@ -189,9 +189,9 @@ public class Typeface { /** @hide */ public static final int STYLE_MASK = 0x03; @UnsupportedAppUsage - private @Style int mStyle = 0; + private @Style final int mStyle; - private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0; + private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) final int mWeight; // Value for weight and italic. Indicates the value is resolved by font metadata. // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp @@ -207,6 +207,7 @@ public class Typeface { private static final int STYLE_NORMAL = 0; private static final int STYLE_ITALIC = 1; + @GuardedBy("this") private int[] mSupportedAxes; private static final int[] EMPTY_AXES = {}; diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index f708298a2cbd..684eebe6ffde 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -37,8 +37,6 @@ interface IKeyChainService { void setUserSelectable(String alias, boolean isUserSelectable); int generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec); - int attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags, - out KeymasterCertificateChain chain); boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain); // APIs used by CertInstaller and DevicePolicyManager @@ -65,6 +63,7 @@ interface IKeyChainService { AppUriAuthenticationPolicy getCredentialManagementAppPolicy(); String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri); void removeCredentialManagementApp(); + boolean isCredentialManagementApp(String packageName); // APIs used by KeyChainActivity void setGrant(int uid, String alias, boolean value); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 97819c56fd5a..65a81cd57f41 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -44,6 +44,8 @@ import android.os.UserManager; import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; import android.util.Log; import com.android.org.conscrypt.TrustedCertificateStore; @@ -421,6 +423,15 @@ public final class KeyChain { * credentials. This is limited to unmanaged devices. The authentication policy must be * provided to be able to make this request successfully. * + * <p> This intent should be started using {@link Activity#startActivityForResult(Intent, int)} + * to verify whether the request was successful and whether the user accepted or denied the + * request. If the user successfully receives and accepts the request, the result code will be + * {@link Activity#RESULT_OK}, otherwise the result code will be + * {@link Activity#RESULT_CANCELED}. + * + * <p> {@link KeyChain#isCredentialManagementApp(Context)} should be used to determine whether + * an app is already the credential management app. + * * @param policy The authentication policy determines which alias for a private key and * certificate pair should be used for authentication. */ @@ -589,6 +600,55 @@ public final class KeyChain { } /** + * Check whether the caller is the credential management app {@link CredentialManagementApp}. + * The credential management app has the ability to manage the user's KeyChain credentials + * on unmanaged devices. + * + * <p> {@link KeyChain#createManageCredentialsIntent} should be used by an app to request to + * become the credential management app. The user must approve this request before the app can + * manage the user's credentials. There can only be one credential management on the device. + * + * @return {@code true} if the caller is the credential management app. + */ + public static boolean isCredentialManagementApp(@NonNull Context context) { + boolean isCredentialManagementApp = false; + try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) { + isCredentialManagementApp = keyChainConnection.getService() + .isCredentialManagementApp(context.getPackageName()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while checking whether the caller is the " + + "credential management app.", e); + } catch (SecurityException e) { + isCredentialManagementApp = false; + } + return isCredentialManagementApp; + } + + /** + * Called by the credential management app to get the authentication policy + * {@link AppUriAuthenticationPolicy}. + * + * @return the credential management app's authentication policy. + * @throws SecurityException if the caller is not the credential management app. + */ + @NonNull + public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy( + @NonNull Context context) throws SecurityException { + AppUriAuthenticationPolicy policy = null; + try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) { + policy = keyChainConnection.getService().getCredentialManagementAppPolicy(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + throw new RuntimeException( + "Interrupted while getting credential management app policy.", e); + } + return policy; + } + + /** * Set a credential management app. The credential management app has the ability to manage * the user's KeyChain credentials on unmanaged devices. * @@ -682,6 +742,33 @@ public final class KeyChain { return null; } + /** + * This prefix is used to disambiguate grant aliase strings from normal key alias strings. + * Technically, a key alias string can use the same prefix. However, a collision does not + * lead to privilege escalation, because grants are access controlled in the Keystore daemon. + * @hide + */ + public static final String GRANT_ALIAS_PREFIX = "ks2_keychain_grant_id:"; + + private static KeyDescriptor getGrantDescriptor(String keyid) { + KeyDescriptor result = new KeyDescriptor(); + result.domain = Domain.GRANT; + result.blob = null; + result.alias = null; + try { + result.nspace = Long.parseUnsignedLong( + keyid.substring(GRANT_ALIAS_PREFIX.length()), 16 /* radix */); + } catch (NumberFormatException e) { + return null; + } + return result; + } + + /** @hide */ + public static String getGrantString(KeyDescriptor key) { + return String.format(GRANT_ALIAS_PREFIX + "%016X", key.nspace); + } + /** @hide */ @Nullable @WorkerThread public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias) @@ -705,11 +792,23 @@ public final class KeyChain { if (keyId == null) { return null; + } + + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + try { + return android.security.keystore2.AndroidKeyStoreProvider + .loadAndroidKeyStoreKeyPairFromKeystore( + KeyStore2.getInstance(), + getGrantDescriptor(keyId)); + } catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) { + throw new KeyChainException(e); + } } else { try { return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( KeyStore.getInstance(), keyId, KeyStore.UID_SELF); - } catch (RuntimeException | UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) { + } catch (RuntimeException | UnrecoverableKeyException + | KeyPermanentlyInvalidatedException e) { throw new KeyChainException(e); } } @@ -827,11 +926,8 @@ public final class KeyChain { @Deprecated public static boolean isBoundKeyAlgorithm( @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) { - if (!isKeyAlgorithmSupported(algorithm)) { - return false; - } - - return KeyStore.getInstance().isHardwareBacked(algorithm); + // All supported algorithms are hardware backed. Individual keys may not be. + return true; } /** @hide */ diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 476e4d7b7b18..6ac3821d0f9c 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.security.keymaster.KeymasterDefs; +import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreService; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyEntryResponse; @@ -157,6 +158,50 @@ public class KeyStore2 { } /** + * Grant string prefix as used by the keystore boringssl engine. Must be kept in sync + * with system/security/keystore-engine. Note: The prefix here includes the 0x which + * std::stringstream used in keystore-engine needs to identify the number as hex represented. + * Here we include it in the prefix, because Long#parseUnsignedLong does not understand it + * and gets the radix as explicit argument. + * @hide + */ + private static final String KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX = + "ks2_keystore-engine_grant_id:0x"; + + /** + * This function turns a grant identifier into a specific string that is understood by the + * keystore-engine in system/security/keystore-engine. Is only used by VPN and WI-FI components + * to allow certain system components like racoon or vendor components like WPA supplicant + * to use keystore keys with boring ssl. + * + * @param grantId the grant id as returned by {@link #grant} in the {@code nspace} filed of + * the resulting {@code KeyDescriptor}. + * @return The grant descriptor string. + * @hide + */ + public static String makeKeystoreEngineGrantString(long grantId) { + return String.format("%s%016X", KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX, grantId); + } + + /** + * Convenience function to turn a keystore engine grant string as returned by + * {@link #makeKeystoreEngineGrantString(long)} back into a grant KeyDescriptor. + * + * @param grantString As string returned by {@link #makeKeystoreEngineGrantString(long)} + * @return The grant key descriptor. + * @hide + */ + public static KeyDescriptor keystoreEngineGrantString2KeyDescriptor(String grantString) { + KeyDescriptor key = new KeyDescriptor(); + key.domain = Domain.GRANT; + key.nspace = Long.parseUnsignedLong( + grantString.substring(KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX.length()), 16); + key.alias = null; + key.blob = null; + return key; + } + + /** * Create a grant that allows the grantee identified by {@code granteeUid} to use * the key specified by {@code descriptor} withint the restrictions given by * {@code accessVectore}. diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index c20cf01a993e..a6e33664f2b1 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -59,7 +59,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(mSpec.getKeystoreAlias()); out.writeInt(mSpec.getPurposes()); - out.writeInt(mSpec.getUid()); + out.writeInt(mSpec.getNamespace()); out.writeInt(mSpec.getKeySize()); // Only needs to support RSAKeyGenParameterSpec and ECGenParameterSpec. @@ -125,7 +125,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { private ParcelableKeyGenParameterSpec(Parcel in) { final String keystoreAlias = in.readString(); final int purposes = in.readInt(); - final int uid = in.readInt(); + final int namespace = in.readInt(); final int keySize = in.readInt(); final int keySpecType = in.readInt(); @@ -177,7 +177,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { // KeyGenParameterSpec constructor (whereas using a builder would silently drop them). mSpec = new KeyGenParameterSpec( keystoreAlias, - uid, + namespace, keySize, algorithmSpec, certificateSubject, diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index b3bfd6a3a97a..e401add9ece7 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -154,7 +154,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private KeyGenParameterSpec mSpec; private String mEntryAlias; - private int mEntryUid; + private int mEntryNamespace; private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; private int mKeymasterAlgorithm = -1; private int mKeySizeBits; @@ -218,7 +218,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } mEntryAlias = spec.getKeystoreAlias(); - mEntryUid = spec.getUid(); + mEntryNamespace = spec.getNamespace(); mSpec = spec; mKeymasterAlgorithm = keymasterAlgorithm; mKeySizeBits = spec.getKeySize(); @@ -439,7 +439,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private void resetAll() { mEntryAlias = null; - mEntryUid = KeyProperties.NAMESPACE_APPLICATION; + mEntryNamespace = KeyProperties.NAMESPACE_APPLICATION; mJcaKeyAlgorithm = null; mKeymasterAlgorithm = -1; mKeymasterPurposes = null; @@ -541,10 +541,10 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeyDescriptor descriptor = new KeyDescriptor(); descriptor.alias = mEntryAlias; - descriptor.domain = mEntryUid == KeyProperties.NAMESPACE_APPLICATION + descriptor.domain = mEntryNamespace == KeyProperties.NAMESPACE_APPLICATION ? Domain.APP : Domain.SELINUX; - descriptor.nspace = mEntryUid; + descriptor.nspace = mEntryNamespace; descriptor.blob = null; boolean success = false; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index e1011155248e..35059ac929c3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -273,10 +273,10 @@ public class AndroidKeyStoreProvider extends Provider { /** @hide **/ @NonNull public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( - @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) + @NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor) throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { AndroidKeyStoreKey key = - loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor); if (key instanceof AndroidKeyStorePublicKey) { AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key; return new KeyPair(publicKey, publicKey.getPrivateKey()); @@ -336,7 +336,7 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore( @NonNull KeyStore2 keyStore, @NonNull String alias, int namespace) - throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { KeyDescriptor descriptor = new KeyDescriptor(); if (namespace == KeyProperties.NAMESPACE_APPLICATION) { @@ -348,6 +348,18 @@ public class AndroidKeyStoreProvider extends Provider { } descriptor.alias = alias; descriptor.blob = null; + + final AndroidKeyStoreKey key = loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor); + if (key instanceof AndroidKeyStorePublicKey) { + return ((AndroidKeyStorePublicKey) key).getPrivateKey(); + } else { + return key; + } + } + + private static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore( + @NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { KeyEntryResponse response = null; try { response = keyStore.getKeyEntry(descriptor); @@ -397,7 +409,7 @@ public class AndroidKeyStoreProvider extends Provider { keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) { return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata, new KeyStoreSecurityLevel(response.iSecurityLevel), - keymasterAlgorithm).getPrivateKey(); + keymasterAlgorithm); } else { throw new UnrecoverableKeyException("Key algorithm unknown"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java index 59271e9fb63c..e6e6d4a1934f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java @@ -22,7 +22,7 @@ import android.os.RemoteException; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PinnedStackListenerForwarder; -import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedStackListener; +import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedTaskListener; /** * The singleton wrapper to communicate between WindowManagerService and WMShell features @@ -46,7 +46,7 @@ public class WindowManagerShellWrapper { * Adds a pinned stack listener, which will receive updates from the window manager service * along with any other pinned stack listeners that were added via this method. */ - public void addPinnedStackListener(PinnedStackListener listener) + public void addPinnedStackListener(PinnedTaskListener listener) throws RemoteException { mPinnedStackListenerForwarder.addListener(listener); mPinnedStackListenerForwarder.register(DEFAULT_DISPLAY); @@ -55,7 +55,7 @@ public class WindowManagerShellWrapper { /** * Removes a pinned stack listener. */ - public void removePinnedStackListener(PinnedStackListener listener) { + public void removePinnedStackListener(PinnedTaskListener listener) { mPinnedStackListenerForwarder.removeListener(listener); } 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 79f9dcd8a1fb..562b32b41dd2 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 @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; 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.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitLayout; @@ -57,12 +58,14 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan private final AppPairsController mController; private final SyncTransactionQueue mSyncQueue; private final DisplayController mDisplayController; + private final DisplayImeController mDisplayImeController; private SplitLayout mSplitLayout; AppPair(AppPairsController controller) { mController = controller; mSyncQueue = controller.getSyncTransactionQueue(); mDisplayController = controller.getDisplayController(); + mDisplayImeController = controller.getDisplayImeController(); } int getRootTaskId() { @@ -97,7 +100,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan mSplitLayout = new SplitLayout(TAG + "SplitDivider", mDisplayController.getDisplayContext(mRootTaskInfo.displayId), mRootTaskInfo.configuration, this /* layoutChangeListener */, - b -> b.setParent(mRootTaskLeash)); + b -> b.setParent(mRootTaskLeash), mDisplayImeController); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java index 0415f12496f2..b159333e9a0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java @@ -23,17 +23,16 @@ import android.util.Slog; import android.util.SparseArray; 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.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.SyncTransactionQueue; import java.io.PrintWriter; -import java.util.concurrent.TimeUnit; /** * Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}. @@ -50,12 +49,15 @@ public class AppPairsController { // Active app-pairs mapped by root task id key. private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>(); private final DisplayController mDisplayController; + private final DisplayImeController mDisplayImeController; public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, - DisplayController displayController, ShellExecutor mainExecutor) { + DisplayController displayController, ShellExecutor mainExecutor, + DisplayImeController displayImeController) { mTaskOrganizer = organizer; mSyncQueue = syncQueue; mDisplayController = displayController; + mDisplayImeController = displayImeController; mMainExecutor = mainExecutor; } @@ -130,18 +132,22 @@ public class AppPairsController { } } - public ShellTaskOrganizer getTaskOrganizer() { + ShellTaskOrganizer getTaskOrganizer() { return mTaskOrganizer; } - public SyncTransactionQueue getSyncTransactionQueue() { + SyncTransactionQueue getSyncTransactionQueue() { return mSyncQueue; } - public DisplayController getDisplayController() { + DisplayController getDisplayController() { return mDisplayController; } + DisplayImeController getDisplayImeController() { + return mDisplayImeController; + } + 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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 047df5ba7ca9..1320780bfb8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1213,7 +1213,7 @@ public class BubbleController { /** PinnedStackListener that dispatches IME visibility updates to the stack. */ //TODO(b/170442945): Better way to do this / insets listener? - private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener { + private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { if (mStackView != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java index 19c3cf9c462a..7d5c9f07f86c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java @@ -227,24 +227,29 @@ public class BubbleFlyoutView extends FrameLayout { /* * Fade animation for consecutive flyouts. */ - void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) { + void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, PointF stackPos) { final Runnable afterFadeOut = () -> { updateFlyoutMessage(flyoutMessage, parentWidth); // Wait for TextViews to layout with updated height. post(() -> { - mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; - fade(true /* in */, () -> {} /* after */); + fade(true /* in */, stackPos, () -> {} /* after */); } /* after */ ); }; - fade(false /* in */, afterFadeOut); + fade(false /* in */, stackPos, afterFadeOut); } /* * Fade-out above or fade-in from below. */ - private void fade(boolean in, Runnable afterFade) { + private void fade(boolean in, PointF stackPos, Runnable afterFade) { + mFlyoutY = stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; + setAlpha(in ? 0f : 1f); setTranslationY(in ? mFlyoutY + FLYOUT_FADE_Y : mFlyoutY); + mRestingTranslationX = mArrowPointingLeft + ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble + : stackPos.x - getWidth() - mFlyoutSpaceFromBubble; + setTranslationX(mRestingTranslationX); animate() .alpha(in ? 1f : 0f) .setDuration(in ? FLYOUT_FADE_IN_DURATION : FLYOUT_FADE_OUT_DURATION) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index a3edc20e242a..e99669f5b5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1539,19 +1539,16 @@ public class BubbleStackView extends FrameLayout * Update bubble order and pointer position. */ public void updateBubbleOrder(List<Bubble> bubbles) { - if (isExpansionAnimating()) { - return; - } final Runnable reorder = () -> { for (int i = 0; i < bubbles.size(); i++) { Bubble bubble = bubbles.get(i); mBubbleContainer.reorderView(bubble.getIconView(), i); } }; - if (mIsExpanded) { + if (mIsExpanded || isExpansionAnimating()) { reorder.run(); updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */); - } else { + } else if (!isExpansionAnimating()) { List<View> bubbleViews = bubbles.stream() .map(b -> b.getIconView()).collect(Collectors.toList()); mStackAnimationController.animateReorder(bubbleViews, reorder); @@ -2434,7 +2431,7 @@ public class BubbleStackView extends FrameLayout if (mFlyout.getVisibility() == View.VISIBLE) { mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(), - mStackAnimationController.getStackPosition().y); + mStackAnimationController.getStackPosition()); } else { mFlyout.setVisibility(INVISIBLE); mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index 58a4baf39614..f118b1e0b7a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -22,6 +22,8 @@ import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.os.Process.SYSTEM_UID; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; +import static android.util.RotationUtils.rotateBounds; +import static android.util.RotationUtils.rotateInsets; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; @@ -37,7 +39,6 @@ import android.graphics.Rect; import android.os.SystemProperties; import android.provider.Settings; import android.util.DisplayMetrics; -import android.util.RotationUtils; import android.util.Size; import android.view.Display; import android.view.DisplayCutout; @@ -49,6 +50,7 @@ import com.android.internal.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Contains information about the layout-properties of a display. This refers to internal layout @@ -81,6 +83,31 @@ public class DisplayLayout { private boolean mHasStatusBar = false; private int mNavBarFrameHeight = 0; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DisplayLayout)) return false; + final DisplayLayout other = (DisplayLayout) o; + return mUiMode == other.mUiMode + && mWidth == other.mWidth + && mHeight == other.mHeight + && Objects.equals(mCutout, other.mCutout) + && mRotation == other.mRotation + && mDensityDpi == other.mDensityDpi + && Objects.equals(mNonDecorInsets, other.mNonDecorInsets) + && Objects.equals(mStableInsets, other.mStableInsets) + && mHasNavigationBar == other.mHasNavigationBar + && mHasStatusBar == other.mHasStatusBar + && mNavBarFrameHeight == other.mNavBarFrameHeight; + } + + @Override + public int hashCode() { + return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi, + mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar, + mNavBarFrameHeight); + } + /** * Create empty layout. */ @@ -241,38 +268,6 @@ public class DisplayLayout { } /** - * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` - * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and - * remains at 0,0 after rotation. - * - * Only 'bounds' is mutated. - */ - public static void rotateBounds(Rect inOutBounds, Rect parentBounds, int delta) { - int rdelta = ((delta % 4) + 4) % 4; - int origLeft = inOutBounds.left; - switch (rdelta) { - case 0: - return; - case 1: - inOutBounds.left = inOutBounds.top; - inOutBounds.top = parentBounds.right - inOutBounds.right; - inOutBounds.right = inOutBounds.bottom; - inOutBounds.bottom = parentBounds.right - origLeft; - return; - case 2: - inOutBounds.left = parentBounds.right - inOutBounds.right; - inOutBounds.right = parentBounds.right - origLeft; - return; - case 3: - inOutBounds.left = parentBounds.bottom - inOutBounds.bottom; - inOutBounds.bottom = inOutBounds.right; - inOutBounds.right = parentBounds.bottom - inOutBounds.top; - inOutBounds.top = origLeft; - return; - } - } - - /** * Calculates the stable insets if we already have the non-decor insets. */ private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets, @@ -359,8 +354,7 @@ public class DisplayLayout { if (rotation == ROTATION_0) { return computeSafeInsets(cutout, displayWidth, displayHeight); } - final Insets waterfallInsets = - RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); + final Insets waterfallInsets = rotateInsets(cutout.getWaterfallInsets(), rotation); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); Rect[] cutoutRects = cutout.getBoundingRectsAll(); final Rect[] newBounds = new Rect[cutoutRects.length]; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index fb70cbe502b0..4bb8e9b6581f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -39,12 +39,12 @@ import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.ScrollCaptureResponse; import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; import android.view.View; import android.view.ViewGroup; -import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.window.ClientWindowFrames; @@ -371,7 +371,11 @@ public class SystemWindows { @Override public void requestScrollCapture(IScrollCaptureCallbacks callbacks) { try { - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse( + new ScrollCaptureResponse.Builder() + .setDescription("Not Implemented") + .build()); + } catch (RemoteException ex) { // ignore } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java index e94080aa8db7..3b670057cb1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -227,7 +227,7 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. } @Override - public void onActivityDismissingDockedStack() { + public void onActivityDismissingDockedTask() { mMainHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index c27c92961c2b..b9fdaa1ab1af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -36,11 +36,13 @@ import androidx.annotation.Nullable; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.DisplayImeController; /** - * Stack divider for app pair. + * Divider for multi window splits. */ -public class DividerView extends FrameLayout implements View.OnTouchListener { +public class DividerView extends FrameLayout implements View.OnTouchListener, + DisplayImeController.ImePositionProcessor { public static final long TOUCH_ANIMATION_DURATION = 150; public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200; @@ -56,6 +58,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { private boolean mMoving; private int mStartPos; private GestureDetector mDoubleTapDetector; + private boolean mInteractive; public DividerView(@NonNull Context context) { super(context); @@ -91,12 +94,19 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mTouchElevation = getResources().getDimensionPixelSize( R.dimen.docked_stack_divider_lift_elevation); mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener()); + mInteractive = true; setOnTouchListener(this); } @Override + public void onImeVisibilityChanged(int displayId, boolean isShowing) { + if (displayId != getDisplay().getDisplayId()) return; + setInteractive(!isShowing); + } + + @Override public boolean onTouch(View v, MotionEvent event) { - if (mSplitLayout == null) { + if (mSplitLayout == null || !mInteractive) { return false; } @@ -202,6 +212,13 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mViewHost.relayout(lp); } + private void setInteractive(boolean interactive) { + if (interactive == mInteractive) return; + mInteractive = interactive; + releaseTouching(); + mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE); + } + private boolean isLandscape() { return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 60231df37370..bacff78e6a67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -35,6 +35,7 @@ import androidx.annotation.Nullable; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.DisplayImeController; /** * Records and handles layout of splits. Helps to calculate proper bounds when configuration or @@ -59,11 +60,13 @@ public class SplitLayout { public SplitLayout(String windowName, Context context, Configuration configuration, LayoutChangeListener layoutChangeListener, - SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks) { + SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, + DisplayImeController displayImeController) { mContext = context.createConfigurationContext(configuration); mLayoutChangeListener = layoutChangeListener; mSplitWindowManager = new SplitWindowManager( - windowName, mContext, configuration, parentContainerCallbacks); + windowName, mContext, configuration, parentContainerCallbacks, + displayImeController); mDividerWindowWidth = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index 87f0c25c93df..f6efb0120dda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -46,6 +46,7 @@ import android.view.WindowlessWindowManager; import androidx.annotation.Nullable; import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayImeController; /** * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split. @@ -53,23 +54,27 @@ import com.android.wm.shell.R; public final class SplitWindowManager extends WindowlessWindowManager { private static final String TAG = SplitWindowManager.class.getSimpleName(); + private final String mWindowName; + private final DisplayImeController mDisplayImeController; private final ParentContainerCallbacks mParentContainerCallbacks; private Context mContext; private SurfaceControlViewHost mViewHost; private SurfaceControl mLeash; private boolean mResizingSplits; - private final String mWindowName; + private DividerView mDividerView; public interface ParentContainerCallbacks { void attachToParentSurface(SurfaceControl.Builder b); } public SplitWindowManager(String windowName, Context context, Configuration config, - ParentContainerCallbacks parentContainerCallbacks) { + ParentContainerCallbacks parentContainerCallbacks, + DisplayImeController displayImeController) { super(config, null /* rootSurface */, null /* hostInputToken */); mContext = context.createConfigurationContext(config); mParentContainerCallbacks = parentContainerCallbacks; mWindowName = windowName; + mDisplayImeController = displayImeController; } @Override @@ -103,14 +108,16 @@ public final class SplitWindowManager extends WindowlessWindowManager { /** Inflates {@link DividerView} on to the root surface. */ void init(SplitLayout splitLayout) { - if (mViewHost == null) { - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + if (mDividerView != null || mViewHost != null) { + throw new UnsupportedOperationException( + "Try to inflate divider view again without release first"); } - final Rect dividerBounds = splitLayout.getDividerBounds(); - final DividerView dividerView = (DividerView) LayoutInflater.from(mContext) + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + mDividerView = (DividerView) LayoutInflater.from(mContext) .inflate(R.layout.split_divider, null /* root */); + final Rect dividerBounds = splitLayout.getDividerBounds(); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH @@ -119,8 +126,9 @@ public final class SplitWindowManager extends WindowlessWindowManager { lp.token = new Binder(); lp.setTitle(mWindowName); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - mViewHost.setView(dividerView, lp); - dividerView.setup(splitLayout, mViewHost); + mViewHost.setView(mDividerView, lp); + mDividerView.setup(splitLayout, mViewHost); + mDisplayImeController.addPositionProcessor(mDividerView); } /** @@ -128,6 +136,11 @@ public final class SplitWindowManager extends WindowlessWindowManager { * hierarchy. */ void release() { + if (mDividerView != null) { + mDisplayImeController.removePositionProcessor(mDividerView); + mDividerView = null; + } + if (mViewHost != null){ mViewHost.release(); mViewHost = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java index 477ec339f1db..40244fbb4503 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java @@ -18,6 +18,7 @@ package com.android.wm.shell.legacysplitscreen; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.util.RotationUtils.rotateBounds; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; @@ -244,7 +245,7 @@ public class LegacySplitDisplayLayout { DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize); tmpRect.set(bounds); - DisplayLayout.rotateBounds(tmpRect, displayRect, rotation - dl.rotation()); + rotateBounds(tmpRect, displayRect, dl.rotation(), rotation); rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height()); final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect, tmpDL.getOrientation()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index c8f89876222e..82468ad999b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -105,7 +105,7 @@ class WindowManagerProxy { synchronized (mDockedRect) { mTouchableRegion.set(region); } - WindowManagerGlobal.getWindowManagerService().setDockedStackDividerTouchRegion( + WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion( mTouchableRegion); } catch (RemoteException e) { Log.w(TAG, "Failed to set touchable region: " + e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index afc8a097dd05..4c5cc226b40f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -202,6 +202,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { animateWindows(token, leash, fromBounds, toBounds, direction, mEnterExitAnimationDurationMs); wct.setBounds(token, toBounds); + wct.setAppBounds(token, toBounds); }); applyTransaction(wct); } @@ -231,6 +232,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { // DisplayRotationController will applyTransaction() after finish rotating if (wct != null) { wct.setBounds(token, null/* reset */); + wct.setAppBounds(token, null/* reset */); } }); tx.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index 8f8ec475a85c..b3b1ba7cd1c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -20,7 +20,7 @@ import android.app.RemoteAction; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.os.RemoteException; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.WindowManagerGlobal; import androidx.annotation.BinderThread; @@ -32,66 +32,66 @@ import java.util.ArrayList; /** * PinnedStackListener that simply forwards all calls to each listener added via * {@link #addListener}. This is necessary since calling - * {@link com.android.server.wm.WindowManagerService#registerPinnedStackListener} replaces any + * {@link com.android.server.wm.WindowManagerService#registerPinnedTaskListener} replaces any * previously set listener. */ public class PinnedStackListenerForwarder { - private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl(); + private final IPinnedTaskListener mListenerImpl = new PinnedTaskListenerImpl(); private final ShellExecutor mMainExecutor; - private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>(); + private final ArrayList<PinnedTaskListener> mListeners = new ArrayList<>(); public PinnedStackListenerForwarder(ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; } /** Adds a listener to receive updates from the WindowManagerService. */ - public void addListener(PinnedStackListener listener) { + public void addListener(PinnedTaskListener listener) { mListeners.add(listener); } /** Removes a listener so it will no longer receive updates from the WindowManagerService. */ - public void removeListener(PinnedStackListener listener) { + public void removeListener(PinnedTaskListener listener) { mListeners.remove(listener); } public void register(int displayId) throws RemoteException { - WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener( + WindowManagerGlobal.getWindowManagerService().registerPinnedTaskListener( displayId, mListenerImpl); } private void onMovementBoundsChanged(boolean fromImeAdjustment) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onMovementBoundsChanged(fromImeAdjustment); } } private void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onImeVisibilityChanged(imeVisible, imeHeight); } } private void onActionsChanged(ParceledListSlice<RemoteAction> actions) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onActionsChanged(actions); } } private void onActivityHidden(ComponentName componentName) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onActivityHidden(componentName); } } private void onAspectRatioChanged(float aspectRatio) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onAspectRatioChanged(aspectRatio); } } @BinderThread - private class PinnedStackListenerImpl extends IPinnedStackListener.Stub { + private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub { @Override public void onMovementBoundsChanged(boolean fromImeAdjustment) { mMainExecutor.execute(() -> { @@ -129,10 +129,10 @@ public class PinnedStackListenerForwarder { } /** - * A counterpart of {@link IPinnedStackListener} with empty implementations. + * A counterpart of {@link IPinnedTaskListener} with empty implementations. * Subclasses can ignore those methods they do not intend to take action upon. */ - public static class PinnedStackListener { + public static class PinnedTaskListener { public void onMovementBoundsChanged(boolean fromImeAdjustment) {} public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 5ffa9885a143..a52db24aa184 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.util.RotationUtils.rotateBounds; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -33,7 +34,6 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.DisplayLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -90,15 +90,15 @@ public class PipAnimationController { private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private PipTransitionAnimator mCurrentAnimator; - - private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = ThreadLocal.withInitial(() -> { AnimationHandler handler = new AnimationHandler(); handler.setProvider(new SfVsyncFrameCallbackProvider()); return handler; }); + private PipTransitionAnimator mCurrentAnimator; + public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; } @@ -268,6 +268,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override @@ -275,6 +276,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override public void onAnimationRepeat(Animator animation) {} @@ -448,7 +450,7 @@ public class PipAnimationController { // Rotate the end bounds according to the rotation delta because the display will // be rotated to the same orientation. rotatedEndRect = new Rect(endValue); - DisplayLayout.rotateBounds(rotatedEndRect, endValue, rotationDelta); + rotateBounds(rotatedEndRect, endValue, rotationDelta); } else { rotatedEndRect = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 4a2a032d8d1c..9a584c67f97c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -65,6 +65,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUtils; import java.io.PrintWriter; +import java.util.Objects; import java.util.function.Consumer; /** @@ -93,17 +94,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; - protected PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener = - new PipControllerPinnedStackListener(); + protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener = + new PipControllerPinnedTaskListener(); /** * Handler for display rotation changes. */ private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { - if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { - // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update - // the display layout in the bounds handler in this case. + if (!mPipTaskOrganizer.isInPip() + || mPipBoundsState.getDisplayLayout().rotation() == toRotation + || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { + // Skip if the same rotation has been set or we aren't in PIP or haven't actually + // entered PIP yet. We still need to update the display layout in the bounds handler + // in this case. onDisplayRotationChangedNotInPip(mContext, toRotation); // do not forget to update the movement bounds as well. updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */, @@ -178,8 +182,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb /** * Handler for messages from the PIP controller. */ - private class PipControllerPinnedStackListener extends - PinnedStackListenerForwarder.PinnedStackListener { + private class PipControllerPinnedTaskListener extends + PinnedStackListenerForwarder.PinnedTaskListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { mPipBoundsState.setImeVisibility(imeVisible, imeHeight); @@ -310,7 +314,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); try { - mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); + mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener); } catch (RemoteException e) { Slog.e(TAG, "Failed to register pinned stack listener", e); } @@ -378,6 +382,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) { + if (Objects.equals(layout, mPipBoundsState.getDisplayLayout())) { + return; + } Runnable updateDisplayLayout = () -> { mPipBoundsState.setDisplayLayout(layout); updateMovementBounds(null /* toBounds */, @@ -476,8 +483,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb int launcherRotation, int shelfHeight) { setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); onDisplayRotationChangedNotInPip(mContext, launcherRotation); - return mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, + final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, pictureInPictureParams); + // sync mPipBoundsState with the newly calculated bounds. + mPipBoundsState.setNormalBounds(entryBounds); + return entryBounds; } private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 56f183fd7303..70980191f103 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -377,7 +377,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private void registerWmShellPinnedStackListener(WindowManagerShellWrapper wmShell) { try { - wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedStackListener() { + wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedTaskListener() { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { if (DEBUG) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index b0167afa2e4e..11548adaf5d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -42,6 +42,7 @@ import androidx.annotation.Nullable; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.draganddrop.DragAndDropPolicy; @@ -62,18 +63,20 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; private final ShellExecutor mMainExecutor; private final SplitScreenImpl mImpl = new SplitScreenImpl(); + private final DisplayImeController mDisplayImeController; private StageCoordinator mStageCoordinator; public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTDAOrganizer, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, DisplayImeController displayImeController) { mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; mRootTDAOrganizer = rootTDAOrganizer; mMainExecutor = mainExecutor; + mDisplayImeController = displayImeController; } public SplitScreen asSplitScreen() { @@ -84,7 +87,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { if (mStageCoordinator == null) { // TODO: Multi-display mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, - mRootTDAOrganizer, mTaskOrganizer); + mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e44c820a656a..22c97515ad76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitLayout; @@ -79,10 +80,12 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, private DisplayAreaInfo mDisplayAreaInfo; private final Context mContext; private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); + private final DisplayImeController mDisplayImeController; private boolean mExitSplitScreenOnHide = true; StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, - RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) { + RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, + DisplayImeController displayImeController) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -90,13 +93,14 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mTaskOrganizer = taskOrganizer; mMainStage = new MainStage(mTaskOrganizer, mDisplayId, mMainStageListener, mSyncQueue); mSideStage = new SideStage(mTaskOrganizer, mDisplayId, mSideStageListener, mSyncQueue); + mDisplayImeController = displayImeController; mRootTDAOrganizer.registerListener(displayId, this); } @VisibleForTesting StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, - MainStage mainStage, SideStage sideStage) { + MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -104,6 +108,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mTaskOrganizer = taskOrganizer; mMainStage = mainStage; mSideStage = sideStage; + mDisplayImeController = displayImeController; mRootTDAOrganizer.registerListener(displayId, this); } @@ -420,7 +425,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, if (mSplitLayout == null) { mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mDisplayAreaInfo.configuration, this, - b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b)); + b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b), + mDisplayImeController); } } 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 45d551528940..76497706ce4f 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 @@ -43,6 +43,7 @@ import java.util.List; /** * Util class to create the view for a splash screen content. + * * @hide */ public class SplashscreenContentDrawer { @@ -349,7 +350,7 @@ public class SplashscreenContentDrawer { // Calculate the difference between two colors based on the HSV dimensions. final float normalizeH = minAngle / 180f; - final double square = Math.pow(normalizeH, 2) + final double square = Math.pow(normalizeH, 2) + Math.pow(aHsv[1] - bHsv[1], 2) + Math.pow(aHsv[2] - bHsv[2], 2); final double mean = square / 3; @@ -433,8 +434,11 @@ public class SplashscreenContentDrawer { */ private interface ColorTester { float nonTransparentRatio(); + boolean isComplexColor(); + int getDominantColor(); + boolean isGrayscale(); } @@ -511,14 +515,17 @@ public class SplashscreenContentDrawer { // restore to original bounds drawable.setBounds(initialBounds); - final Palette.Builder builder = new Palette.Builder(bitmap) - .maximumColorCount(5).clearFilters(); + final Palette.Builder builder; // The Palette API will ignore Alpha, so it cannot handle transparent pixels, but // sometimes we will need this information to know if this Drawable object is // transparent. mFilterTransparent = filterTransparent; if (mFilterTransparent) { - builder.setQuantizer(TRANSPARENT_FILTER_QUANTIZER); + builder = new Palette.Builder(bitmap, TRANSPARENT_FILTER_QUANTIZER) + .maximumColorCount(5); + } else { + builder = new Palette.Builder(bitmap, null) + .maximumColorCount(5); } mPalette = builder.generate(); bitmap.recycle(); @@ -538,7 +545,7 @@ public class SplashscreenContentDrawer { public int getDominantColor() { final Palette.Swatch mainSwatch = mPalette.getDominantSwatch(); if (mainSwatch != null) { - return mainSwatch.getRgb(); + return mainSwatch.getInt(); } return Color.BLACK; } @@ -549,7 +556,7 @@ public class SplashscreenContentDrawer { if (swatches != null) { for (int i = swatches.size() - 1; i >= 0; i--) { Palette.Swatch swatch = swatches.get(i); - if (!isGrayscaleColor(swatch.getRgb())) { + if (!isGrayscaleColor(swatch.getInt())) { return false; } } @@ -561,9 +568,9 @@ public class SplashscreenContentDrawer { private static final int NON_TRANSPARENT = 0xFF000000; private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer(); private float mNonTransparentRatio; + @Override - public void quantize(final int[] pixels, final int maxColors, - final Palette.Filter[] filters) { + public void quantize(final int[] pixels, final int maxColors) { mNonTransparentRatio = 0; int realSize = 0; for (int i = pixels.length - 1; i > 0; i--) { @@ -575,7 +582,7 @@ public class SplashscreenContentDrawer { if (DEBUG) { Slog.d(TAG, "quantize: this is pure transparent image"); } - mInnerQuantizer.quantize(pixels, maxColors, filters); + mInnerQuantizer.quantize(pixels, maxColors); return; } mNonTransparentRatio = (float) realSize / pixels.length; @@ -587,7 +594,7 @@ public class SplashscreenContentDrawer { rowIndex++; } } - mInnerQuantizer.quantize(samplePixels, maxColors, filters); + mInnerQuantizer.quantize(samplePixels, maxColors); } @Override diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index a57ac35583b2..9dd25fe0e6fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "WMShellFlickerTests", srcs: ["src/**/*.java", "src/**/*.kt"], diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 10aea519f18b..35bab7aaf22c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -18,8 +18,6 @@ package com.android.wm.shell.flicker import android.graphics.Region import android.view.Surface -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.traces.layers.getVisibleBounds @@ -32,15 +30,15 @@ fun FlickerTestParameter.appPairsDividerIsVisible() { fun FlickerTestParameter.appPairsDividerIsInvisible() { assertLayersEnd { - this.notExists(APP_PAIR_SPLIT_DIVIDER) + this.notContains(APP_PAIR_SPLIT_DIVIDER) } } fun FlickerTestParameter.appPairsDividerBecomesVisible() { assertLayers { - this.hidesLayer(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER) .then() - .showsLayer(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER) } } @@ -52,30 +50,30 @@ fun FlickerTestParameter.dockedStackDividerIsVisible() { fun FlickerTestParameter.dockedStackDividerBecomesVisible() { assertLayers { - this.hidesLayer(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER) .then() - .showsLayer(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.dockedStackDividerBecomesInvisible() { assertLayers { - this.showsLayer(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER) .then() - .hidesLayer(DOCKED_STACK_DIVIDER) + .isInvisible(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.dockedStackDividerIsInvisible() { assertLayersEnd { - this.notExists(DOCKED_STACK_DIVIDER) + this.notContains(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation)) + this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) } } @@ -85,7 +83,7 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation)) + this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) } } @@ -95,7 +93,7 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation)) + this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) } } @@ -105,7 +103,7 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation)) + this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index d2cfb0fbb5f6..03b93c74233c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -18,3 +18,5 @@ package com.android.wm.shell.flicker const val IME_WINDOW_NAME = "InputMethod" const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" +const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider" +const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 5d51b2fd515f..90e71373b1fd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice @@ -48,7 +47,7 @@ class AppPairsTestCannotPairNonResizeableApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 77890ba8ed15..dc51b4fb5a9e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,17 +16,16 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import org.junit.FixMethodOrder @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class AppPairsTestPairPrimaryAndSecondaryApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -75,10 +74,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps( fun appsEndingBounds() { testSpec.assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(primaryApp.defaultWindowName, - appPairsHelper.getPrimaryBounds(dividerRegion)) - .hasVisibleRegion(secondaryApp.defaultWindowName, - appPairsHelper.getSecondaryBounds(dividerRegion)) + this.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), + primaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), + secondaryApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 3d3ca0cfd450..5bb9b2f8b8ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -16,17 +16,16 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.wm.shell.flicker.appPairsDividerIsInvisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import org.junit.FixMethodOrder @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class AppPairsTestUnpairPrimaryAndSecondaryApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) setup { @@ -80,10 +79,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( fun appsStartingBounds() { testSpec.assertLayersStart { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - hasVisibleRegion(primaryApp.defaultWindowName, - appPairsHelper.getPrimaryBounds(dividerRegion)) - hasVisibleRegion(secondaryApp.defaultWindowName, - appPairsHelper.getSecondaryBounds(dividerRegion)) + coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), + primaryApp.defaultWindowName) + coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), + secondaryApp.defaultWindowName) } } @@ -91,8 +90,8 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test fun appsEndingBounds() { testSpec.assertLayersEnd { - notExists(primaryApp.defaultWindowName) - notExists(secondaryApp.defaultWindowName) + notContains(primaryApp.defaultWindowName) + notContains(secondaryApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 9e6752db224f..91e080f65550 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper import android.util.Log @@ -66,7 +65,7 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) } } - internal open val transition: FlickerBuilder.(Bundle) -> Unit + internal open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 35a0020b16a9..5f003ba62b2d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface @@ -52,7 +51,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppsInAppPairsMode( testSpec: FlickerTestParameter ) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -72,16 +71,16 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @FlakyTest + @Presubmit @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() - @FlakyTest + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - @FlakyTest + @Presubmit @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index 326a775acc8d..d4792088ac31 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface @@ -55,7 +54,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( testSpec: FlickerTestParameter ) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -77,17 +76,8 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( @Presubmit @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(isRotated) - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(isRotated) - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( + Surface.ROTATION_0, testSpec.config.endRotation) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt index 271b25fc0ce1..83853e61ab5e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -30,7 +29,7 @@ abstract class RotateTwoLaunchedAppsTransition( override val nonResizeableApp: SplitScreenHelper? get() = null - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 9b70fac737e6..17c51fb15b0c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -50,11 +49,10 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -// @FlakyTest(bugId = 179116910) class EnterSplitScreenDockActivity( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index bd57a59ea3d9..a94fd463c624 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class EnterSplitScreenLaunchToSide( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt index 67578b29a36c..238059b484b5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -50,7 +49,7 @@ import org.junit.runners.Parameterized class EnterSplitScreenNonResizableNotDock( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) teardown { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index 5d42a4a8fae0..acd570a3773e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -34,6 +32,7 @@ import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -52,7 +51,7 @@ import org.junit.runners.Parameterized class ExitLegacySplitScreenFromBottom( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index ff8f9c6ed865..cef188695ce7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class ExitPrimarySplitScreenShowSecondaryFullscreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) teardown { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt index 893b101d0759..1e89a25c06df 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -27,7 +26,7 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen abstract class LegacySplitScreenRotateTransition( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { eachRun { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 09a7e31d20e2..7f69a66e6e82 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface @@ -66,7 +65,7 @@ class LegacySplitScreenToLauncher( .launcherStrategy.supportedLauncherPackage private val testApp = SimpleAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index 6ab1f0bfdb89..91ea8716e4f0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.app.Instrumentation -import android.os.Bundle import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry @@ -41,7 +40,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation) .launcherStrategy.supportedLauncherPackage - protected open val transition: FlickerBuilder.(Bundle) -> Unit + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { @@ -70,7 +69,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } } - internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit + internal open val cleanSetup: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt index 1b4b54a74eab..caafa278d297 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -34,6 +32,7 @@ import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class NonResizableDismissInLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt index 2365e3bfd8c3..543484ac9759 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -33,6 +31,7 @@ import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class NonResizableLaunchInLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index b369a3d32e64..d22833784bcf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class OpenAppToLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index ffffa1902976..f5174bc3cef7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -17,13 +17,11 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.graphics.Region -import android.os.Bundle import android.util.Rational import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -46,6 +44,7 @@ import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEn import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder import org.junit.Test @@ -70,7 +69,7 @@ class ResizeLegacySplitScreen( private val testAppTop = SimpleAppHelper(instrumentation) private val testAppBottom = ImeAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { @@ -151,21 +150,21 @@ class ResizeLegacySplitScreen( @Test fun topAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(sSimpleActivity) + this.isVisible(sSimpleActivity) } } @Test fun bottomAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(sImeActivity) + this.isVisible(sImeActivity) } } @Test fun dividerLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER) } } @@ -183,8 +182,8 @@ class ResizeLegacySplitScreen( dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.hasVisibleRegion("SimpleActivity", topAppBounds) - .hasVisibleRegion("ImeActivity", bottomAppBounds) + this.coversExactly(topAppBounds, "SimpleActivity") + .coversExactly(bottomAppBounds, "ImeActivity") } } @@ -203,8 +202,8 @@ class ResizeLegacySplitScreen( displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.hasVisibleRegion(sSimpleActivity, topAppBounds) - .hasVisibleRegion(sImeActivity, bottomAppBounds) + this.coversExactly(topAppBounds, sSimpleActivity) + .coversExactly(bottomAppBounds, sImeActivity) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index c538008aa179..c914adae2b7c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class RotateOneLaunchedAppAndEnterSplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index c1162560119c..ffb20a4bc99a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class RotateOneLaunchedAppInSplitScreenMode( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index 273925c0361c..8cf1990d406f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppAndEnterSplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index c7188dc227e7..9c798d8ea661 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppInSplitScreenMode( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index ca48eaa45840..75c33c671008 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -51,7 +50,7 @@ class EnterExitPipTest( private val testApp = FixedAppHelper(instrumentation) private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { setup { eachRun { @@ -68,7 +67,7 @@ class EnterExitPipTest( @Test fun pipAppRemainInsideVisibleBounds() { testSpec.assertWm { - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -95,10 +94,10 @@ class EnterExitPipTest( @Test fun showBothAppLayersThenHidePip() { testSpec.assertLayers { - showsLayer(testApp.defaultWindowName) - .showsLayer(pipApp.defaultWindowName) + isVisible(testApp.defaultWindowName) + .isVisible(pipApp.defaultWindowName) .then() - .hidesLayer(testApp.defaultWindowName) + .isInvisible(testApp.defaultWindowName) } } @@ -106,8 +105,8 @@ class EnterExitPipTest( @Test fun testAppCoversFullScreenWithPipOnDisplay() { testSpec.assertLayersStart { - hasVisibleRegion(testApp.defaultWindowName, displayBounds) - coversAtMostRegion(displayBounds, pipApp.defaultWindowName) + coversExactly(displayBounds, testApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -115,7 +114,7 @@ class EnterExitPipTest( @Test fun pipAppCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(pipApp.defaultWindowName, displayBounds) + coversExactly(displayBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index e1fbc16e8ce2..83dca53b1542 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -48,7 +47,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true, stringExtras = emptyMap()) { transitions { pipApp.clickEnterPipButton() @@ -85,7 +84,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Test fun pipLayerBecomesVisible() { testSpec.assertLayers { - this.showsLayer(pipApp.launcherName) + this.isVisible(pipApp.launcherName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 215b97bfeb83..9011f1a9fb6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -16,10 +16,8 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -56,7 +54,7 @@ class EnterPipToOtherOrientationTest( private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setupAndTeardown(this, configuration) @@ -121,24 +119,24 @@ class EnterPipToOtherOrientationTest( @Test fun pipAppLayerHidesTestApp() { testSpec.assertLayersStart { - hasVisibleRegion(pipApp.defaultWindowName, startingBounds) + coversExactly(startingBounds, pipApp.defaultWindowName) isInvisible(testApp.defaultWindowName) } } - @FlakyTest + @Presubmit @Test fun testAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(testApp.defaultWindowName, endingBounds) + coversExactly(endingBounds, testApp.defaultWindowName) } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt index 707d28d9c4c0..96eb66c3cc28 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt @@ -48,7 +48,7 @@ fun WindowManagerStateSubject.isInPipMode( activity: ComponentName ): WindowManagerStateSubject = apply { val windowName = activity.toWindowName() - hasWindow(windowName) + contains(windowName) val pinnedWindows = wmState.pinnedWindows .map { it.title } Truth.assertWithMessage("Window not in PIP mode") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt index 968a11de2511..3e331761f767 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt @@ -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. @@ -16,41 +16,27 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import org.junit.FixMethodOrder import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipToHomeTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit +abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { configuration -> setup { eachRun { @@ -62,30 +48,27 @@ class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { this.setRotation(Surface.ROTATION_0) } } - transitions { - pipApp.closePipWindow(wmHelper) - } } - @Postsubmit + @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() @Presubmit @Test - fun pipWindowBecomesInvisible() { + open fun pipWindowBecomesInvisible() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) .then() @@ -95,32 +78,32 @@ class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun pipLayerBecomesInvisible() { + open fun pipLayerBecomesInvisible() { testSpec.assertLayers { - this.showsLayer(PIP_WINDOW_TITLE) + this.isVisible(PIP_WINDOW_TITLE) .then() - .hidesLayer(PIP_WINDOW_TITLE) + .isInvisible(PIP_WINDOW_TITLE) } } @Presubmit @Test - fun statusBarLayerRotatesScales() = + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test - fun noUncoveredRegions() = + open fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + open fun navBarLayerRotatesAndScales() = + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) @FlakyTest(bugId = 151179149) @Test - fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") + open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt new file mode 100644 index 000000000000..0408421c72a5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt @@ -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.wm.shell.flicker.pip + +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipCloseWithDismissButton` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipCloseWithDismissButtonTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + pipApp.closePipWindow(wmHelper) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt new file mode 100644 index 000000000000..afaf33a7c46f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt @@ -0,0 +1,90 @@ +/* + * 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.flicker.pip + +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipCloseWithSwipe` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds + val pipCenterX = pipRegion.centerX() + val pipCenterY = pipRegion.centerY() + val displayCenterX = device.displayWidth / 2 + device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 5) + } + } + + @Postsubmit + @Test + override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() + + @Postsubmit + @Test + override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() + + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() + + @Postsubmit + @Test + override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible() + + @Postsubmit + @Test + override fun statusBarLayerRotatesScales() = + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Postsubmit + @Test + override fun noUncoveredRegions() = super.noUncoveredRegions() + + @Postsubmit + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index f3b9ea1455ca..46339603f806 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { configuration -> setup { test { @@ -78,7 +77,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun pipInVisibleBounds() { testSpec.assertWm { val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index daf381ee9ddb..97afc65b8b61 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -58,7 +57,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t private val testApp = FixedAppHelper(instrumentation) private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { withTestName { testSpec.name } repeat { testSpec.config.repetitions } @@ -90,7 +89,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun pipWindowInsideDisplayBounds() { testSpec.assertWm { - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -100,7 +99,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t testSpec.assertWmEnd { isVisible(testApp.defaultWindowName) isVisible(imeApp.defaultWindowName) - noWindowsOverlap(setOf(testApp.defaultWindowName, imeApp.defaultWindowName)) + noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName) } } @@ -116,7 +115,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun pipLayerInsideDisplayBounds() { testSpec.assertLayers { - coversAtMostRegion(displayBounds, pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -124,8 +123,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun bothAppLayersVisible() { testSpec.assertLayersEnd { - coversAtMostRegion(displayBounds, testApp.defaultWindowName) - coversAtMostRegion(displayBounds, imeApp.defaultWindowName) + coversAtMost(displayBounds, testApp.defaultWindowName) + coversAtMost(displayBounds, imeApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt new file mode 100644 index 000000000000..4c95da284d9e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt @@ -0,0 +1,94 @@ +/* + * 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.flicker.pip + +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.google.common.truth.Truth +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipMovesInAllApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + private val taplInstrumentation = LauncherInstrumentation() + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition(eachRun = false) { + teardown { + eachRun { + taplInstrumentation.pressHome() + } + } + transitions { + taplInstrumentation.pressHome().switchToAllApps() + wmHelper.waitForAppTransitionIdle() + } + } + + @Postsubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) } + + @Postsubmit + @Test + fun pipWindowMovesUp() = testSpec.assertWmEnd { + val initialState = this.trace?.first()?.wmState + ?: error("Trace should not be empty") + val startPos = initialState.pinnedWindows.first().frame + val currPos = this.wmState.pinnedWindows.first().frame + val subject = Truth.assertWithMessage("Pip should have moved up") + subject.that(currPos.top).isGreaterThan(startPos.top) + subject.that(currPos.bottom).isGreaterThan(startPos.bottom) + subject.that(currPos.left).isEqualTo(startPos.left) + subject.that(currPos.right).isEqualTo(startPos.right) + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index 43c12acef9e8..df835d21e73f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) private val startingBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) private val endingBounds = WindowUtils.getDisplayBounds(testSpec.config.endRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { configuration -> setup { test { @@ -89,11 +88,11 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, testSpec.config.endRotation, allStates = false) - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @@ -113,8 +112,8 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun appLayerRotates_StartingBounds() { testSpec.assertLayersStart { - hasVisibleRegion(fixedApp.defaultWindowName, startingBounds) - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) + coversExactly(startingBounds, fixedApp.defaultWindowName) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -122,8 +121,8 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun appLayerRotates_EndingBounds() { testSpec.assertLayersEnd { - hasVisibleRegion(fixedApp.defaultWindowName, endingBounds) - coversAtMostRegion(endingBounds, pipApp.defaultWindowName) + coversExactly(endingBounds, fixedApp.defaultWindowName) + coversAtMost(endingBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 02389a9ccf87..1bb1d2861f3f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -50,7 +49,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { configuration -> setup { eachRun { @@ -67,7 +66,7 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @@ -102,18 +101,18 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Test fun appReplacesPipLayer() { testSpec.assertLayers { - this.showsLayer(PIP_WINDOW_TITLE) + this.isVisible(PIP_WINDOW_TITLE) .then() - .showsLayer(pipApp.launcherName) + .isVisible(pipApp.launcherName) } } - @FlakyTest + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index a94483ec00a0..b0a9afef9215 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent -import android.os.Bundle import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -38,7 +37,7 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected val isRotated = testSpec.config.startRotation.isRotated() protected val pipApp = PipAppHelper(instrumentation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) - protected abstract val transition: FlickerBuilder.(Bundle) -> Unit + protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit // Helper class to process test actions by broadcast. protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { @@ -81,7 +80,7 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { /** * Gets a configuration that handles basic setup and teardown of pip tests */ - protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit + protected val setupAndTeardown: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { test { @@ -112,8 +111,8 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected open fun buildTransition( eachRun: Boolean, stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), - extraSpec: FlickerBuilder.(Bundle) -> Unit = {} - ): FlickerBuilder.(Bundle) -> Unit { + extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {} + ): FlickerBuilder.(Map<String, Any?>) -> Unit { return { configuration -> setupAndTeardown(this, configuration) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 1f0370dc0505..7916ce59af52 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ class SetRequestedOrientationWhilePinnedTest( private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setupAndTeardown(this, configuration) @@ -88,7 +87,7 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { - coversAtMostRegion(pipApp.defaultWindowName, startingBounds) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -112,7 +111,7 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -120,15 +119,15 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(pipApp.defaultWindowName, endingBounds) + coversExactly(endingBounds, pipApp.defaultWindowName) } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp index 26627a47ee62..ea606df1536d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "WMShellFlickerTestApp", srcs: ["**/*.java"], @@ -23,4 +32,4 @@ java_library { name: "wmshell-flicker-test-components", srcs: ["src/**/Components.java"], sdk_version: "test_current", -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java index e094158e1144..27c626170a4b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java @@ -20,17 +20,17 @@ import static org.mockito.Mockito.mock; 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.SyncTransactionQueue; -import org.mockito.Mock; - public class TestAppPairsController extends AppPairsController { private TestAppPairsPool mPool; public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, DisplayController displayController) { - super(organizer, syncQueue, displayController, mock(ShellExecutor.class)); + super(organizer, syncQueue, displayController, mock(ShellExecutor.class), + mock(DisplayImeController.class)); mPool = new TestAppPairsPool(this); setPairsPool(mPool); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java index 2b5b77e49e3a..88e754c58792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java @@ -38,6 +38,12 @@ import com.android.internal.R; import org.junit.Test; +/** + * Tests for {@link DisplayLayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:DisplayLayoutTest + */ @SmallTest public class DisplayLayoutTest { @@ -70,18 +76,6 @@ public class DisplayLayoutTest { @Test public void testRotate() { // Basic rotate utility - Rect testParent = new Rect(0, 0, 1000, 600); - Rect testInner = new Rect(40, 20, 120, 80); - Rect testResult = new Rect(testInner); - DisplayLayout.rotateBounds(testResult, testParent, 1); - assertEquals(new Rect(20, 880, 80, 960), testResult); - testResult.set(testInner); - DisplayLayout.rotateBounds(testResult, testParent, 2); - assertEquals(new Rect(880, 20, 960, 80), testResult); - testResult.set(testInner); - DisplayLayout.rotateBounds(testResult, testParent, 3); - assertEquals(new Rect(520, 40, 580, 120), testResult); - Resources res = createResources(40, 50, false, 30, 40); DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0); DisplayLayout dl = new DisplayLayout(info, res, true, true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java index 21bc32c6563c..d8aebc284bf1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java @@ -198,7 +198,7 @@ public class TaskStackListenerImplTest { @Test public void testOnActivityDismissingDockedStack() { - mImpl.onActivityDismissingDockedStack(); + mImpl.onActivityDismissingDockedTask(); verify(mCallback).onActivityDismissingDockedStack(); verify(mOtherCallback).onActivityDismissingDockedStack(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 5821eed6f611..7b0e6b9a5ed7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; import org.junit.Test; @@ -49,6 +50,7 @@ import org.mockito.MockitoAnnotations; public class SplitLayoutTests extends ShellTestCase { @Mock SplitLayout.LayoutChangeListener mLayoutChangeListener; @Mock SurfaceControl mRootLeash; + @Mock DisplayImeController mDisplayImeController; private SplitLayout mSplitLayout; @Before @@ -59,7 +61,8 @@ public class SplitLayoutTests extends ShellTestCase { mContext, getConfiguration(false), mLayoutChangeListener, - b -> b.setParent(mRootLeash)); + b -> b.setParent(mRootLeash), + mDisplayImeController); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java index 698315a77d8e..86d0d82222e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -29,6 +29,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; import org.junit.Test; @@ -42,6 +43,7 @@ import org.mockito.MockitoAnnotations; public class SplitWindowManagerTests extends ShellTestCase { @Mock SurfaceControl mSurfaceControl; @Mock SplitLayout mSplitLayout; + @Mock DisplayImeController mDisplayImeController; private SplitWindowManager mSplitWindowManager; @Before @@ -50,7 +52,7 @@ public class SplitWindowManagerTests extends ShellTestCase { final Configuration configuration = new Configuration(); configuration.setToDefaults(); mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration, - b -> b.setParent(mSurfaceControl)); + b -> b.setParent(mSurfaceControl), mDisplayImeController); when(mSplitLayout.getDividerBounds()).thenReturn( new Rect(0, 0, configuration.windowConfiguration.getBounds().width(), configuration.windowConfiguration.getBounds().height())); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 3147dab1a0f8..63b94139dd9c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.util.RotationUtils.rotateBounds; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; @@ -37,7 +38,6 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayLayout; import org.junit.Before; import org.junit.Test; @@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends ShellTestCase { // Apply fraction 1 to compute the end value. animator.applySurfaceControlTransaction(mLeash, new DummySurfaceControlTx(), 1); final Rect rotatedEndBounds = new Rect(endBounds); - DisplayLayout.rotateBounds(rotatedEndBounds, endBounds, ROTATION_90); + rotateBounds(rotatedEndBounds, endBounds, ROTATION_90); assertEquals("Expect 90 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index cfe84639d24e..f2b4e9761226 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -124,7 +124,7 @@ public class PipControllerTest extends ShellTestCase { final ComponentName component1 = new ComponentName(mContext, "component1"); when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - mPipController.mPinnedStackListener.onActivityHidden(component1); + mPipController.mPinnedTaskListener.onActivityHidden(component1); verify(mMockPipBoundsState).setLastPipComponentName(null); } @@ -135,7 +135,7 @@ public class PipControllerTest extends ShellTestCase { final ComponentName component2 = new ComponentName(mContext, "component2"); when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - mPipController.mPinnedStackListener.onActivityHidden(component2); + mPipController.mPinnedTaskListener.onActivityHidden(component2); verify(mMockPipBoundsState, never()).setLastPipComponentName(null); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index d2d18129d071..74753aac4a24 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -41,6 +41,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.SyncTransactionQueue; import org.junit.Before; @@ -58,13 +59,14 @@ public class StageCoordinatorTests extends ShellTestCase { @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock private MainStage mMainStage; @Mock private SideStage mSideStage; + @Mock private DisplayImeController mDisplayImeController; private StageCoordinator mStageCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); mStageCoordinator = new TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, - mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage); + mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, mDisplayImeController); } @Test @@ -94,9 +96,9 @@ public class StageCoordinatorTests extends ShellTestCase { TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, - MainStage mainStage, SideStage sideStage) { + MainStage mainStage, SideStage sideStage, DisplayImeController imeController) { super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage, - sideStage); + sideStage, imeController); // Prepare default TaskDisplayArea for testing. mDisplayAreaInfo = new DisplayAreaInfo( diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING index 777aa0b429e5..766714cd7694 100644 --- a/libs/androidfw/TEST_MAPPING +++ b/libs/androidfw/TEST_MAPPING @@ -1,10 +1,6 @@ { "presubmit": [ { - "name": "libandroidfw_tests", - "host": true - }, - { "name": "CtsResourcesLoaderTests" } ] diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp index b36ff0968ba3..3035a79f668d 100644 --- a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp +++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_libs_androidfw_license"], +} + cc_fuzz { name: "cursorwindow_fuzzer", srcs: [ diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp index 5d3f6f2f28c9..2448cc904104 100644 --- a/libs/hwui/FrameInfo.cpp +++ b/libs/hwui/FrameInfo.cpp @@ -20,13 +20,12 @@ namespace android { namespace uirenderer { -const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames = { +const std::array FrameInfoNames{ "Flags", "FrameTimelineVsyncId", "IntendedVsync", "Vsync", - "OldestInputEvent", - "NewestInputEvent", + "InputEventId", "HandleInputStart", "AnimationStart", "PerformTraversalsStart", @@ -40,7 +39,8 @@ const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> Fram "DequeueBufferDuration", "QueueBufferDuration", "GpuCompleted", - "SwapBuffersCompleted" + "SwapBuffersCompleted", + "DisplayPresentTime", }; static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20, diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 45a367f525da..912d04c5d87d 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -28,15 +28,14 @@ namespace android { namespace uirenderer { -#define UI_THREAD_FRAME_INFO_SIZE 11 +static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 10; enum class FrameInfoIndex { Flags = 0, FrameTimelineVsyncId, IntendedVsync, Vsync, - OldestInputEvent, - NewestInputEvent, + InputEventId, HandleInputStart, AnimationStart, PerformTraversalsStart, @@ -56,13 +55,14 @@ enum class FrameInfoIndex { GpuCompleted, SwapBuffersCompleted, + DisplayPresentTime, // Must be the last value! // Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT NumIndexes }; -extern const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames; +extern const std::array<const char*, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames; namespace FrameInfoFlags { enum { diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 2cd9b7b39174..4eefe921fbe9 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -112,7 +112,7 @@ void JankTracker::finishFrame(const FrameInfo& frame) { std::lock_guard lock(mDataMutex); // Fast-path for jank-free frames - int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted); + int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::SwapBuffersCompleted); if (mDequeueTimeForgiveness && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) { nsecs_t expectedDequeueDuration = mDequeueTimeForgiveness + frame[FrameInfoIndex::Vsync] - frame[FrameInfoIndex::IssueDrawCommandsStart]; @@ -219,7 +219,7 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description, void JankTracker::dumpFrames(int fd) { dprintf(fd, "\n\n---PROFILEDATA---\n"); for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { - dprintf(fd, "%s", FrameInfoNames[i].c_str()); + dprintf(fd, "%s", FrameInfoNames[i]); dprintf(fd, ","); } for (size_t i = 0; i < mFrames.size(); i++) { diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 1fddac4cd05d..28d2b4cec0e1 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -188,7 +188,7 @@ void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const SkPaint& paint) { } if (mCanvas->getSaveCount() == restoreCount + 1) { - SkCanvasPriv::DrawBehind(mCanvas, *filterPaint(paint)); + SkCanvasPriv::DrawBehind(mCanvas, filterPaint(paint)); this->restore(); } } @@ -431,15 +431,14 @@ void SkiaCanvas::drawColor(int color, SkBlendMode mode) { mCanvas->drawColor(color, mode); } -SkiaCanvas::PaintCoW&& SkiaCanvas::filterPaint(PaintCoW&& paint) const { +void SkiaCanvas::onFilterPaint(SkPaint& paint) { if (mPaintFilter) { - mPaintFilter->filter(&paint.writeable()); + mPaintFilter->filter(&paint); } - return std::move(paint); } void SkiaCanvas::drawPaint(const SkPaint& paint) { - mCanvas->drawPaint(*filterPaint(paint)); + mCanvas->drawPaint(filterPaint(paint)); } // ---------------------------------------------------------------------------- @@ -457,13 +456,11 @@ void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint, points += 2; } - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawPoints(mode, count, pts.get(), p); - }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawPoints(mode, count, pts.get(), p); }); } void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); }); } void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) { @@ -472,9 +469,8 @@ void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawLine(startX, startY, stopX, stopY, p); - }); + applyLooper(&paint, + [&](const SkPaint& p) { mCanvas->drawLine(startX, startY, stopX, stopY, p); }); } void SkiaCanvas::drawLines(const float* points, int count, const Paint& paint) { @@ -484,46 +480,44 @@ void SkiaCanvas::drawLines(const float* points, int count, const Paint& paint) { void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; - apply_looper(&paint, [&](const SkPaint& p) { + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawRect({left, top, right, bottom}, p); }); } void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); }); } void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawRoundRect(rect, rx, ry, p); - }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawRoundRect(rect, rx, ry, p); }); } void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); }); } void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) { if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return; - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); }); } void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); }); } void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, bool useCenter, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect arc = SkRect::MakeLTRB(left, top, right, bottom); - apply_looper(&paint, [&](const SkPaint& p) { + applyLooper(&paint, [&](const SkPaint& p) { if (fabs(sweepAngle) >= 360.0f) { mCanvas->drawOval(arc, p); } else { @@ -537,13 +531,11 @@ void SkiaCanvas::drawPath(const SkPath& path, const Paint& paint) { if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) { return; } - apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); }); } void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawVertices(vertices, mode, p); - }); + applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); }); } // ---------------------------------------------------------------------------- @@ -552,7 +544,7 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) { auto image = bitmap.makeImage(); - apply_looper(paint, [&](const SkPaint& p) { + applyLooper(paint, [&](const SkPaint& p) { auto sampling = SkSamplingOptions(p.getFilterQuality()); mCanvas->drawImage(image, left, top, sampling, &p); }); @@ -562,7 +554,7 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* auto image = bitmap.makeImage(); SkAutoCanvasRestore acr(mCanvas, true); mCanvas->concat(matrix); - apply_looper(paint, [&](const SkPaint& p) { + applyLooper(paint, [&](const SkPaint& p) { auto sampling = SkSamplingOptions(p.getFilterQuality()); mCanvas->drawImage(image, 0, 0, sampling, &p); }); @@ -575,7 +567,7 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float s SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - apply_looper(paint, [&](const SkPaint& p) { + applyLooper(paint, [&](const SkPaint& p) { auto sampling = SkSamplingOptions(p.getFilterQuality()); mCanvas->drawImageRect(image, srcRect, dstRect, sampling, &p, SkCanvas::kFast_SrcRectConstraint); @@ -672,11 +664,11 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight, pnt.setShader(image->makeShader(sampling)); auto v = builder.detach(); - apply_looper(&pnt, [&](const SkPaint& p) { + applyLooper(&pnt, [&](const SkPaint& p) { SkPaint copy(p); auto s = SkSamplingOptions(p.getFilterQuality()); if (s != sampling) { - // apply_looper changed the quality? + // applyLooper changed the quality? copy.setShader(image->makeShader(s)); } mCanvas->drawVertices(v, SkBlendMode::kModulate, copy); @@ -707,7 +699,7 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa lattice.fBounds = nullptr; SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); auto image = bitmap.makeImage(); - apply_looper(paint, [&](const SkPaint& p) { + applyLooper(paint, [&](const SkPaint& p) { auto filter = SkSamplingOptions(p.getFilterQuality()).filter; mCanvas->drawImageLattice(image.get(), lattice, dst, filter, &p); }); @@ -746,9 +738,7 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai sk_sp<SkTextBlob> textBlob(builder.make()); - apply_looper(&paintCopy, [&](const SkPaint& p) { - mCanvas->drawTextBlob(textBlob, 0, 0, p); - }); + applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); }); drawTextDecorations(x, y, totalAdvance, paintCopy); } @@ -788,9 +778,7 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, sk_sp<SkTextBlob> textBlob(builder.make()); - apply_looper(&paintCopy, [&](const SkPaint& p) { - mCanvas->drawTextBlob(textBlob, 0, 0, p); - }); + applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); }); } // ---------------------------------------------------------------------------- diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index eac3f2217bd8..9ab2b106dbfa 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -169,53 +169,24 @@ protected: const Paint& paint, const SkPath& path, size_t start, size_t end) override; - /** This class acts as a copy on write SkPaint. - * - * Initially this will be the SkPaint passed to the contructor. - * The first time writable() is called this will become a copy of the - * initial SkPaint (or a default SkPaint if nullptr). - */ - struct PaintCoW { - PaintCoW(const SkPaint& that) : mPtr(&that) {} - PaintCoW(const SkPaint* ptr) : mPtr(ptr) {} - PaintCoW(const PaintCoW&) = delete; - PaintCoW(PaintCoW&&) = delete; - PaintCoW& operator=(const PaintCoW&) = delete; - PaintCoW& operator=(PaintCoW&&) = delete; - SkPaint& writeable() { - if (!mStorage) { - if (!mPtr) { - mStorage.emplace(); - } else { - mStorage.emplace(*mPtr); - } - mPtr = &*mStorage; - } - return *mStorage; - } - operator const SkPaint*() const { return mPtr; } - const SkPaint* operator->() const { assert(mPtr); return mPtr; } - explicit operator bool() { return mPtr != nullptr; } - private: - const SkPaint* mPtr; - std::optional<SkPaint> mStorage; - }; + void onFilterPaint(SkPaint& paint); - /** Filters the paint using the current paint filter. - * - * @param paint the paint to filter. Will be initialized with the default - * SkPaint before filtering if filtering is required. - */ - PaintCoW&& filterPaint(PaintCoW&& paint) const; + SkPaint filterPaint(const SkPaint& src) { + SkPaint dst(src); + this->onFilterPaint(dst); + return dst; + } // proc(const SkPaint& modifiedPaint) - template <typename Proc> void apply_looper(const Paint* paint, Proc proc) { - SkPaint skp; - BlurDrawLooper* looper = nullptr; - if (paint) { - skp = *filterPaint(paint); - looper = paint->getLooper(); + template <typename Proc> + void applyLooper(const Paint* paint, Proc proc, void (*preFilter)(SkPaint&) = nullptr) { + BlurDrawLooper* looper = paint ? paint->getLooper() : nullptr; + const SkPaint* skpPtr = paint; + SkPaint skp = skpPtr ? *skpPtr : SkPaint(); + if (preFilter) { + preFilter(skp); } + this->onFilterPaint(skp); if (looper) { looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) { mCanvas->save(); @@ -228,7 +199,6 @@ protected: } } - private: struct SaveRec { int saveCount; diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index df66981853bb..f24ba5c1c878 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -240,8 +240,8 @@ static void android_view_ThreadedRenderer_setSdrWhitePoint(JNIEnv* env, jobject static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) { LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE, - "Mismatched size expectations, given %d expected %d", - frameInfoSize, UI_THREAD_FRAME_INFO_SIZE); + "Mismatched size expectations, given %d expected %zu", frameInfoSize, + UI_THREAD_FRAME_INFO_SIZE); RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo()); return proxy->syncAndDrawFrame(); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 04e3a1cb887e..af7271e96cb9 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -170,36 +170,23 @@ void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { // Recording Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- -SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint) { - bool fixBlending = false; - bool fixAA = false; - if (paint) { - // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and - // older. - fixBlending = sApiLevel <= 27 && paint->getBlendMode() == SkBlendMode::kClear; - fixAA = paint->isAntiAlias(); +void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) { + // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and + // older. + if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) { + paint.setBlendMode(SkBlendMode::kDstOut); } - if (fixBlending || fixAA) { - SkPaint& tmpPaint = paint.writeable(); - - if (fixBlending) { - tmpPaint.setBlendMode(SkBlendMode::kDstOut); - } - - // disabling AA on bitmap draws matches legacy HWUI behavior - tmpPaint.setAntiAlias(false); - } - - return filterPaint(std::move(paint)); + // disabling AA on bitmap draws matches legacy HWUI behavior + paint.setAntiAlias(false); } -static SkFilterMode Paint_to_filter(const SkPaint* paint) { - return paint && paint->getFilterQuality() != kNone_SkFilterQuality ? SkFilterMode::kLinear - : SkFilterMode::kNearest; +static SkFilterMode Paint_to_filter(const SkPaint& paint) { + return paint.getFilterQuality() != kNone_SkFilterQuality ? SkFilterMode::kLinear + : SkFilterMode::kNearest; } -static SkSamplingOptions Paint_to_sampling(const SkPaint* paint) { +static SkSamplingOptions Paint_to_sampling(const SkPaint& paint) { // Android only has 1-bit for "filter", so we don't try to cons-up mipmaps or cubics return SkSamplingOptions(Paint_to_filter(paint), SkMipmapMode::kNone); } @@ -207,9 +194,12 @@ static SkSamplingOptions Paint_to_sampling(const SkPaint* paint) { void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) { sk_sp<SkImage> image = bitmap.makeImage(); - applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) { - mRecorder.drawImage(image, left + x, top + y, Paint_to_sampling(p), p, bitmap.palette()); - }); + applyLooper( + paint, + [&](const SkPaint& p) { + mRecorder.drawImage(image, left, top, Paint_to_sampling(p), &p, bitmap.palette()); + }, + FilterForImage); // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means // it is not safe to store a raw SkImage pointer, because the image object will be destroyed @@ -225,9 +215,12 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con sk_sp<SkImage> image = bitmap.makeImage(); - applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) { - mRecorder.drawImage(image, x, y, Paint_to_sampling(p), p, bitmap.palette()); - }); + applyLooper( + paint, + [&](const SkPaint& p) { + mRecorder.drawImage(image, 0, 0, Paint_to_sampling(p), &p, bitmap.palette()); + }, + FilterForImage); if (!bitmap.isImmutable() && image.get() && !image->unique()) { mDisplayList->mMutableImages.push_back(image.get()); @@ -242,10 +235,13 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop sk_sp<SkImage> image = bitmap.makeImage(); - applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) { - mRecorder.drawImageRect(image, srcRect, dstRect.makeOffset(x, y), Paint_to_sampling(p), - p, SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); - }); + applyLooper( + paint, + [&](const SkPaint& p) { + mRecorder.drawImageRect(image, srcRect, dstRect, Paint_to_sampling(p), &p, + SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); + }, + FilterForImage); if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() && !dstRect.isEmpty()) { @@ -281,10 +277,12 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch // HWUI always draws 9-patches with linear filtering, regardless of the Paint. const SkFilterMode filter = SkFilterMode::kLinear; - applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) { - mRecorder.drawImageLattice(image, lattice, dst.makeOffset(x, y), filter, p, - bitmap.palette()); - }); + applyLooper( + paint, + [&](const SkPaint& p) { + mRecorder.drawImageLattice(image, lattice, dst, filter, &p, bitmap.palette()); + }, + FilterForImage); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 1e404b845084..ff03e0c5f6d6 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -87,22 +87,7 @@ private: std::unique_ptr<SkiaDisplayList> mDisplayList; StartReorderBarrierDrawable* mCurrentBarrier; - template <typename Proc> - void applyLooper(const Paint* paint, Proc proc) { - SkPaint skp; - BlurDrawLooper* looper = nullptr; - if (paint) { - skp = *filterBitmap(paint); - looper = paint->getLooper(); - } - if (looper) { - looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) { - proc(offset.fX, offset.fY, &modifiedPaint); - }); - } else { - proc(0, 0, &skp); - } - } + static void FilterForImage(SkPaint&); /** * A new SkiaDisplayList is created or recycled if available. @@ -113,7 +98,7 @@ private: */ void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height); - PaintCoW&& filterBitmap(PaintCoW&& paint); + using INHERITED = SkiaCanvas; }; } // namespace skiapipeline diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b760db287bcb..f69ddacf7ca1 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -484,7 +484,8 @@ void CanvasContext::draw() { // TODO(b/165985262): measure performance impact const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId); if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) { - const auto inputEventId = mCurrentFrameInfo->get(FrameInfoIndex::NewestInputEvent); + const auto inputEventId = + static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId)); native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId, inputEventId); } @@ -591,7 +592,6 @@ void CanvasContext::draw() { } void CanvasContext::finishFrame(FrameInfo* frameInfo) { - // TODO (b/169858044): Consolidate this into a single call. mJankTracker.finishFrame(*frameInfo); mJankTracker.finishGpuDraw(*frameInfo); diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index ab23448ab93f..1a3dbe7faaf6 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -33,7 +33,7 @@ namespace { constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf"; -constexpr char kRegularFont[] = "/system/fonts/NotoSerif-Regular.ttf"; +constexpr char kRegularFont[] = "/system/fonts/NotoSerif.ttf"; constexpr char kBoldFont[] = "/system/fonts/NotoSerif-Bold.ttf"; constexpr char kItalicFont[] = "/system/fonts/NotoSerif-Italic.ttf"; constexpr char kBoldItalicFont[] = "/system/fonts/NotoSerif-BoldItalic.ttf"; diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index adf58da6a072..38b48e97771a 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -48,13 +48,13 @@ import android.os.ICancellationSignal; */ interface ILocationManager { - @nullable Location getLastLocation(String provider, in LastLocationRequest request, String packageName, String attributionTag); - @nullable ICancellationSignal getCurrentLocation(String provider, in LocationRequest request, in ILocationCallback callback, String packageName, String attributionTag, String listenerId); + @nullable Location getLastLocation(String provider, in LastLocationRequest request, String packageName, @nullable String attributionTag); + @nullable ICancellationSignal getCurrentLocation(String provider, in LocationRequest request, in ILocationCallback callback, String packageName, @nullable String attributionTag, String listenerId); - void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId); + void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, @nullable String attributionTag, String listenerId); void unregisterLocationListener(in ILocationListener listener); - void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, String attributionTag); + void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, @nullable String attributionTag); void unregisterLocationPendingIntent(in PendingIntent pendingIntent); void injectLocation(in Location location); @@ -79,24 +79,24 @@ interface ILocationManager @nullable List<GnssAntennaInfo> getGnssAntennaInfos(); - void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, @nullable String attributionTag); + void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, @nullable String attributionTag, String listenerId); void unregisterGnssStatusCallback(in IGnssStatusListener callback); - void registerGnssNmeaCallback(in IGnssNmeaListener callback, String packageName, @nullable String attributionTag); + void registerGnssNmeaCallback(in IGnssNmeaListener callback, String packageName, @nullable String attributionTag, String listenerId); void unregisterGnssNmeaCallback(in IGnssNmeaListener callback); - void addGnssMeasurementsListener(in GnssMeasurementRequest request, in IGnssMeasurementsListener listener, String packageName, @nullable String attributionTag); + void addGnssMeasurementsListener(in GnssMeasurementRequest request, in IGnssMeasurementsListener listener, String packageName, @nullable String attributionTag, String listenerId); void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener); void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections); - void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, @nullable String attributionTag); + void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, @nullable String attributionTag, String listenerId); void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener); void addProviderRequestListener(in IProviderRequestListener listener); void removeProviderRequestListener(in IProviderRequestListener listener); int getGnssBatchSize(); - void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, @nullable String attributionTag, @nullable String listenerId); + void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, @nullable String attributionTag, String listenerId); void flushGnssBatch(); void stopGnssBatch(); @@ -117,7 +117,8 @@ interface ILocationManager boolean isLocationEnabledForUser(int userId); void setLocationEnabledForUser(boolean enabled, int userId); - void addTestProvider(String name, in ProviderProperties properties, String packageName, @nullable String attributionTag); + void addTestProvider(String name, in ProviderProperties properties, + in List<String> locationTags, String packageName, @nullable String attributionTag); void removeTestProvider(String provider, String packageName, @nullable String attributionTag); void setTestProviderLocation(String provider, in Location location, String packageName, @nullable String attributionTag); void setTestProviderEnabled(String provider, boolean enabled, String packageName, @nullable String attributionTag); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index b7823400695d..95bae5ae7aab 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -51,7 +51,7 @@ import android.content.pm.PackageManager; import android.location.provider.IProviderRequestListener; import android.location.provider.ProviderProperties; import android.location.provider.ProviderRequest; -import android.location.provider.ProviderRequest.Listener; +import android.location.provider.ProviderRequest.ChangedListener; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; @@ -76,6 +76,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -1957,12 +1958,32 @@ public class LocationManager { * allowed} for your app. */ public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) { + addTestProvider(provider, properties, Collections.emptySet()); + } + + /** + * Creates a test location provider and adds it to the set of active providers. This provider + * will replace any provider with the same name that exists prior to this call. + * + * @param provider the provider name + * @param properties the provider properties + * @param locationTags the attribution tags for accessing location from the provider + * + * @throws IllegalArgumentException if provider is null + * @throws IllegalArgumentException if properties is null + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + */ + public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties, + @NonNull Set<String> locationTags) { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(properties != null, "invalid null properties"); + Preconditions.checkArgument(locationTags != null, "invalid null location tags"); try { - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, new ArrayList<>(locationTags), + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2779,33 +2800,32 @@ public class LocationManager { } /** - * Registers a {@link ProviderRequest.Listener} to all providers. + * Adds a {@link ProviderRequest.ChangedListener} for listening to all providers' + * {@link ProviderRequest} changed events. * * @param executor the executor that the callback runs on * @param listener the listener to register - * @return {@code true} always * @hide */ @SystemApi @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) - public boolean registerProviderRequestListener( + public void addProviderRequestChangedListener( @NonNull @CallbackExecutor Executor executor, - @NonNull Listener listener) { + @NonNull ChangedListener listener) { ProviderRequestLazyLoader.sProviderRequestListeners.addListener(listener, new ProviderRequestTransport(executor, listener)); - return true; } /** - * Unregisters a {@link ProviderRequest.Listener}. + * Removes a {@link ProviderRequest.ChangedListener} that has been added. * * @param listener the listener to remove. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) - public void unregisterProviderRequestListener( - @NonNull Listener listener) { + public void removeProviderRequestChangedListener( + @NonNull ProviderRequest.ChangedListener listener) { ProviderRequestLazyLoader.sProviderRequestListeners.removeListener(listener); } @@ -2926,7 +2946,8 @@ public class LocationManager { protected void registerTransport(GnssStatusTransport transport) throws RemoteException { getService().registerGnssStatusCallback(transport, transport.getPackage(), - transport.getAttributionTag()); + transport.getAttributionTag(), + AppOpsManager.toReceiverId(transport.getListener())); } @Override @@ -2947,7 +2968,8 @@ public class LocationManager { protected void registerTransport(GnssNmeaTransport transport) throws RemoteException { getService().registerGnssNmeaCallback(transport, transport.getPackage(), - transport.getAttributionTag()); + transport.getAttributionTag(), + AppOpsManager.toReceiverId(transport.getListener())); } @Override @@ -2968,7 +2990,8 @@ public class LocationManager { protected void registerTransport(GnssMeasurementsTransport transport) throws RemoteException { getService().addGnssMeasurementsListener(transport.getRequest(), transport, - transport.getPackage(), transport.getAttributionTag()); + transport.getPackage(), transport.getAttributionTag(), + AppOpsManager.toReceiverId(transport.getListener())); } @Override @@ -3008,7 +3031,8 @@ public class LocationManager { protected void registerTransport(GnssNavigationTransport transport) throws RemoteException { getService().addGnssNavigationMessageListener(transport, - transport.getPackage(), transport.getAttributionTag()); + transport.getPackage(), transport.getAttributionTag(), + AppOpsManager.toReceiverId(transport.getListener())); } @Override @@ -3442,13 +3466,13 @@ public class LocationManager { } private static class ProviderRequestTransport extends IProviderRequestListener.Stub - implements ListenerTransport<ProviderRequest.Listener> { + implements ListenerTransport<ChangedListener> { private final Executor mExecutor; - private volatile @Nullable ProviderRequest.Listener mListener; + private volatile @Nullable ProviderRequest.ChangedListener mListener; - ProviderRequestTransport(Executor executor, ProviderRequest.Listener listener) { + ProviderRequestTransport(Executor executor, ChangedListener listener) { Preconditions.checkArgument(executor != null, "invalid null executor"); Preconditions.checkArgument(listener != null, "invalid null callback"); mExecutor = executor; @@ -3461,7 +3485,7 @@ public class LocationManager { } @Override - public @Nullable ProviderRequest.Listener getListener() { + public @Nullable ProviderRequest.ChangedListener getListener() { return mListener; } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index a6a0e7aa24ff..763835c9cbe2 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -21,6 +21,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; +import com.android.internal.annotations.Immutable; + +import java.util.Set; + /** * Location manager local system service interface. * @@ -39,6 +43,21 @@ public abstract class LocationManagerInternal { } /** + * Interface for getting callbacks when a location provider's location tags change. + * + * @see LocationTagInfo + */ + public interface OnProviderLocationTagsChangeListener { + + /** + * Called when the location tags for a provider change. + * + * @param providerLocationTagInfo The tag info for a provider. + */ + void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + } + + /** * Returns true if the given provider is enabled for the given user. * * @param provider A location provider as listed by {@link LocationManager#getAllProviders()} @@ -88,4 +107,60 @@ public abstract class LocationManagerInternal { * provider, and the elapsed nanos since boot the current time was computed at. */ public abstract @Nullable LocationTime getGnssTimeMillis(); + + /** + * Sets a listener for changes in the location providers' tags. Passing + * {@code null} clears the current listener. + * + * @param listener The listener. + */ + public abstract void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener); + + /** + * This class represents the location permission tags used by the location provider + * packages in a given UID. These tags are strictly used for accessing state guarded + * by the location permission(s) by a location provider which are required for the + * provider to fulfill its function as being a location provider. + */ + @Immutable + public static class LocationTagInfo { + private final int mUid; + + @NonNull + private final String mPackageName; + + @Nullable + private final Set<String> mLocationTags; + + public LocationTagInfo(int uid, @NonNull String packageName, + @Nullable Set<String> locationTags) { + mUid = uid; + mPackageName = packageName; + mLocationTags = locationTags; + } + + /** + * @return The UID for which tags are related. + */ + public int getUid() { + return mUid; + } + + /** + * @return The package for which tags are related. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * @return The tags for the package used for location related accesses. + */ + @Nullable + public Set<String> getTags() { + return mLocationTags; + } + } } diff --git a/location/java/android/location/provider/ProviderRequest.java b/location/java/android/location/provider/ProviderRequest.java index b6ec32309b08..b72d36519e72 100644 --- a/location/java/android/location/provider/ProviderRequest.java +++ b/location/java/android/location/provider/ProviderRequest.java @@ -56,10 +56,13 @@ public final class ProviderRequest implements Parcelable { /** * Listener to be invoked when a new request is set to the provider. */ - public interface Listener { + public interface ChangedListener { /** * Invoked when a new request is set. + * + * @param provider the location provider associated with the request + * @param request the new {@link ProviderRequest} */ void onProviderRequestChanged(@NonNull String provider, @NonNull ProviderRequest request); } diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java index 0bb7dbb71ae8..85a083ee84bc 100644 --- a/location/java/android/location/util/identity/CallerIdentity.java +++ b/location/java/android/location/util/identity/CallerIdentity.java @@ -42,7 +42,16 @@ public final class CallerIdentity { @VisibleForTesting public static CallerIdentity forTest(int uid, int pid, String packageName, @Nullable String attributionTag) { - return new CallerIdentity(uid, pid, packageName, attributionTag, null); + return forTest(uid, pid, packageName, attributionTag, null); + } + + /** + * Construct a CallerIdentity for test purposes. + */ + @VisibleForTesting + public static CallerIdentity forTest(int uid, int pid, String packageName, + @Nullable String attributionTag, @Nullable String listenerId) { + return new CallerIdentity(uid, pid, packageName, attributionTag, listenerId); } /** @@ -145,7 +154,10 @@ public final class CallerIdentity { return mAttributionTag; } - /** The calling listener id. */ + /** + * The calling listener id. A null listener id will match any other listener id for the purposes + * of {@link #equals(Object)}. + */ public String getListenerId() { return mListenerId; } @@ -168,6 +180,17 @@ public final class CallerIdentity { } } + /** + * Returns a CallerIdentity corrosponding to this CallerIdentity but with a null listener id. + */ + public CallerIdentity stripListenerId() { + if (mListenerId == null) { + return this; + } else { + return new CallerIdentity(mUid, mPid, mPackageName, mAttributionTag, null); + } + } + @Override public String toString() { int length = 10 + mPackageName.length(); @@ -201,15 +224,12 @@ public final class CallerIdentity { return false; } CallerIdentity that = (CallerIdentity) o; - return equalsIgnoringListenerId(that) && Objects.equals(mListenerId, that.mListenerId); - } - - public boolean equalsIgnoringListenerId(CallerIdentity that) { - return that != null - && mUid == that.mUid + return mUid == that.mUid && mPid == that.mPid && mPackageName.equals(that.mPackageName) - && Objects.equals(mAttributionTag, that.mAttributionTag); + && Objects.equals(mAttributionTag, that.mAttributionTag) + && (mListenerId == null || that.mListenerId == null || mListenerId.equals( + that.mListenerId)); } @Override diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f87f90d8e0a2..b2de49deefca 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -568,7 +568,7 @@ public class AudioManager { public static final int FLAG_FROM_KEY = 1 << 12; /** @hide */ - @IntDef(flag = false, prefix = "FLAG", value = { + @IntDef(flag = true, prefix = "FLAG", value = { FLAG_SHOW_UI, FLAG_ALLOW_RINGER_MODES, FLAG_PLAY_SOUND, diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 6fcb75616f0d..58174a0cf408 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -3526,8 +3526,9 @@ public class AudioTrack extends PlayerBase native_enableDeviceCallback(); return true; } catch (IllegalStateException e) { - // Fail silently as track state could have changed in between start - // and enabling routing callback, return false to indicate not enabled + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "testEnableNativeRoutingCallbacks failed", e); + } } } return false; @@ -3577,7 +3578,7 @@ public class AudioTrack extends PlayerBase Handler handler) { synchronized (mRoutingChangeListeners) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { - testEnableNativeRoutingCallbacksLocked(); + mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked(); mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler != null ? handler : new Handler(mInitializationLooper))); diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 44f8385b715e..9ab4aac891e5 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -131,7 +131,59 @@ public class ImageWriter implements AutoCloseable { */ public static @NonNull ImageWriter newInstance(@NonNull Surface surface, @IntRange(from = 1) int maxImages) { - return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN); + return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN, -1 /*width*/, + -1 /*height*/); + } + + /** + * <p> + * Create a new ImageWriter with given number of max Images, format and producer dimension. + * </p> + * <p> + * The {@code maxImages} parameter determines the maximum number of + * {@link Image} objects that can be be dequeued from the + * {@code ImageWriter} simultaneously. Requesting more buffers will use up + * more memory, so it is important to use only the minimum number necessary. + * </p> + * <p> + * The format specifies the image format of this ImageWriter. The format + * from the {@code surface} will be overridden with this format. For example, + * if the surface is obtained from a {@link android.graphics.SurfaceTexture}, the default + * format may be {@link PixelFormat#RGBA_8888}. If the application creates an ImageWriter + * with this surface and {@link ImageFormat#PRIVATE}, this ImageWriter will be able to operate + * with {@link ImageFormat#PRIVATE} Images. + * </p> + * <p> + * Note that the consumer end-point may or may not be able to support Images with different + * format, for such case, the application should only use this method if the consumer is able + * to consume such images. + * </p> + * <p> The input Image size can also be set by the client. </p> + * + * @param surface The destination Surface this writer produces Image data + * into. + * @param maxImages The maximum number of Images the user will want to + * access simultaneously for producing Image data. This should be + * as small as possible to limit memory use. Once maxImages + * Images are dequeued by the user, one of them has to be queued + * back before a new Image can be dequeued for access via + * {@link #dequeueInputImage()}. + * @param format The format of this ImageWriter. It can be any valid format specified by + * {@link ImageFormat} or {@link PixelFormat}. + * + * @param width Input size width. + * @param height Input size height. + * + * @return a new ImageWriter instance. + * + * @hide + */ + public static @NonNull ImageWriter newInstance(@NonNull Surface surface, + @IntRange(from = 1) int maxImages, @Format int format, int width, int height) { + if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { + throw new IllegalArgumentException("Invalid format is specified: " + format); + } + return new ImageWriter(surface, maxImages, format, width, height); } /** @@ -180,13 +232,13 @@ public class ImageWriter implements AutoCloseable { if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { throw new IllegalArgumentException("Invalid format is specified: " + format); } - return new ImageWriter(surface, maxImages, format); + return new ImageWriter(surface, maxImages, format, -1 /*width*/, -1 /*height*/); } /** * @hide */ - protected ImageWriter(Surface surface, int maxImages, int format) { + protected ImageWriter(Surface surface, int maxImages, int format, int width, int height) { if (surface == null || maxImages < 1) { throw new IllegalArgumentException("Illegal input argument: surface " + surface + ", maxImages: " + maxImages); @@ -196,7 +248,8 @@ public class ImageWriter implements AutoCloseable { // Note that the underlying BufferQueue is working in synchronous mode // to avoid dropping any buffers. - mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format); + mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format, width, + height); // nativeInit internally overrides UNKNOWN format. So does surface format query after // nativeInit and before getEstimatedNativeAllocBytes(). @@ -919,7 +972,7 @@ public class ImageWriter implements AutoCloseable { // Native implemented ImageWriter methods. private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs, - int format); + int format, int width, int height); private synchronized native void nativeClose(long nativeCtx); diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 67f1660fff78..f8a642a68dd7 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -980,6 +980,74 @@ public final class MediaFormat { public static final String KEY_BITRATE_MODE = "bitrate-mode"; /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This key applies to all three video frame types (I, P, and B). This value fills + * in for any of the frame-specific #KEY_VIDEO_QP_I_MAX, #KEY_VIDEO_QP_P_MAX, or + * #KEY_VIDEO_QP_B_MAX keys that are not specified + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_MAX = "video-qp-max"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This key applies to all three video frame types (I, P, and B). This value fills + * in for any of the frame-specific #KEY_VIDEO_QP_I_MIN, #KEY_VIDEO_QP_P_MIN, or + * #KEY_VIDEO_QP_B_MIN keys that are not specified + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_MIN = "video-qp-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video I-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video I-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_I_MIN = "video-qp-i-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video P-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_P_MAX = "video-qp-p-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video P-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video B-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video B-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; + + /** * A key describing the audio session ID of the AudioTrack associated * to a tunneled video codec. * The associated value is an integer. diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index c51c9dd06c24..9176dae8609f 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -19,6 +19,7 @@ package android.media; import static android.Manifest.permission.BIND_IMS_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -91,6 +92,7 @@ import java.util.Scanner; import java.util.Set; import java.util.UUID; import java.util.Vector; +import java.util.concurrent.Executor; /** @@ -1358,6 +1360,7 @@ public class MediaPlayer extends PlayerBase private void startImpl() { baseStart(0); // unknown device at this point stayAwake(true); + tryToEnableNativeRoutingCallback(); _start(); } @@ -1383,6 +1386,7 @@ public class MediaPlayer extends PlayerBase stayAwake(false); _stop(); baseStop(); + tryToDisableNativeRoutingCallback(); } private native void _stop() throws IllegalStateException; @@ -1524,8 +1528,9 @@ public class MediaPlayer extends PlayerBase native_enableDeviceCallback(true); return true; } catch (IllegalStateException e) { - // Fail silently as media player state could have changed in between start - // and enabling routing callback, return false to indicate not enabled + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "testEnableNativeRoutingCallbacks failed", e); + } } } return false; @@ -1588,7 +1593,7 @@ public class MediaPlayer extends PlayerBase Handler handler) { synchronized (mRoutingChangeListeners) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { - testEnableNativeRoutingCallbacksLocked(); + mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked(); mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler != null ? handler : mEventHandler)); @@ -2172,7 +2177,7 @@ public class MediaPlayer extends PlayerBase mOnVideoSizeChangedListener = null; mOnTimedTextListener = null; mOnRtpRxNoticeListener = null; - mOnRtpRxNoticeHandler = null; + mOnRtpRxNoticeExecutor = null; synchronized (mTimeProviderLock) { if (mTimeProvider != null) { mTimeProvider.close(); @@ -3481,9 +3486,6 @@ public class MediaPlayer extends PlayerBase case MEDIA_STOPPED: { - tryToDisableNativeRoutingCallback(); - // FIXME see b/179218630 - //baseStop(); TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onStopped(); @@ -3492,18 +3494,9 @@ public class MediaPlayer extends PlayerBase break; case MEDIA_STARTED: - { - // FIXME see b/179218630 - //baseStart(native_getRoutedDeviceId()); - tryToEnableNativeRoutingCallback(); - } // fall through case MEDIA_PAUSED: { - // FIXME see b/179218630 - //if (msg.what == MEDIA_PAUSED) { - // basePause(); - //} TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onPaused(msg.what == MEDIA_PAUSED); @@ -3711,7 +3704,6 @@ public class MediaPlayer extends PlayerBase case MEDIA_RTP_RX_NOTICE: final OnRtpRxNoticeListener rtpRxNoticeListener = mOnRtpRxNoticeListener; - final Handler rtpRxNoticeHandler = mOnRtpRxNoticeHandler; if (rtpRxNoticeListener == null) { return; } @@ -3730,14 +3722,9 @@ public class MediaPlayer extends PlayerBase } finally { parcel.recycle(); } - if (rtpRxNoticeHandler == null) { - rtpRxNoticeListener.onRtpRxNotice(mMediaPlayer, noticeType, data); - } else { - rtpRxNoticeHandler.post( - () -> - rtpRxNoticeListener - .onRtpRxNotice(mMediaPlayer, noticeType, data)); - } + mOnRtpRxNoticeExecutor.execute(() -> + rtpRxNoticeListener + .onRtpRxNotice(mMediaPlayer, noticeType, data)); } return; @@ -4305,28 +4292,26 @@ public class MediaPlayer extends PlayerBase * * @see OnRtpRxNoticeListener * - * @param listener the listener called after a notice from RTP Rx - * @param handler the {@link Handler} that receives RTP Tx events. If null is passed, - * notifications will be posted on the thread that created this MediaPlayer - * instance. If the creating thread does not have a {@link Looper}, then - * notifications will be posted on the main thread. + * @param listener the listener called after a notice from RTP Rx. + * @param executor the {@link Executor} on which to post RTP Tx events. * @hide */ @SystemApi @RequiresPermission(BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener( @NonNull Context context, - @NonNull OnRtpRxNoticeListener listener, @Nullable Handler handler) { + @NonNull @CallbackExecutor Executor executor, + @NonNull OnRtpRxNoticeListener listener) { Objects.requireNonNull(context); Preconditions.checkArgument( context.checkSelfPermission(BIND_IMS_SERVICE) == PERMISSION_GRANTED, BIND_IMS_SERVICE + " permission not granted."); mOnRtpRxNoticeListener = Objects.requireNonNull(listener); - mOnRtpRxNoticeHandler = handler; + mOnRtpRxNoticeExecutor = Objects.requireNonNull(executor); } private OnRtpRxNoticeListener mOnRtpRxNoticeListener; - private Handler mOnRtpRxNoticeHandler; + private Executor mOnRtpRxNoticeExecutor; /** * Register a callback to be invoked when a selected track has timed metadata available. diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS index 6a351d396836..e5d037003ac4 100644 --- a/media/java/android/media/soundtrigger/OWNERS +++ b/media/java/android/media/soundtrigger/OWNERS @@ -1 +1,2 @@ +ytai@google.com elaurent@google.com diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java index 042d02fa3d48..1d2657a280a2 100644 --- a/media/java/android/mtp/MtpServer.java +++ b/media/java/android/mtp/MtpServer.java @@ -144,11 +144,6 @@ public class MtpServer implements Runnable { native_remove_storage(storage.getStorageId()); } - public static void configure(boolean usePtp) { - native_configure(usePtp); - } - - public static native final void native_configure(boolean usePtp); private native final void native_setup( MtpDatabase database, FileDescriptor controlFd, diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 5d959a3930f4..b291ac95bf4f 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -364,7 +364,7 @@ static void ImageWriter_classInit(JNIEnv* env, jclass clazz) { } static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface, - jint maxImages, jint userFormat) { + jint maxImages, jint userFormat, jint userWidth, jint userHeight) { status_t res; ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages); @@ -405,20 +405,38 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje // Get the dimension and format of the producer. sp<ANativeWindow> anw = producer; int32_t width, height, surfaceFormat; - if ((res = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, &width)) != OK) { - ALOGE("%s: Query Surface width failed: %s (%d)", __FUNCTION__, strerror(-res), res); - jniThrowRuntimeException(env, "Failed to query Surface width"); - return 0; + if (userWidth < 0) { + if ((res = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, &width)) != OK) { + ALOGE("%s: Query Surface width failed: %s (%d)", __FUNCTION__, strerror(-res), res); + jniThrowRuntimeException(env, "Failed to query Surface width"); + return 0; + } + } else { + width = userWidth; } + ctx->setBufferWidth(width); - if ((res = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, &height)) != OK) { - ALOGE("%s: Query Surface height failed: %s (%d)", __FUNCTION__, strerror(-res), res); - jniThrowRuntimeException(env, "Failed to query Surface height"); - return 0; + if (userHeight < 0) { + if ((res = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, &height)) != OK) { + ALOGE("%s: Query Surface height failed: %s (%d)", __FUNCTION__, strerror(-res), res); + jniThrowRuntimeException(env, "Failed to query Surface height"); + return 0; + } + } else { + height = userHeight; } ctx->setBufferHeight(height); + if ((userWidth > 0) && (userHeight > 0)) { + res = native_window_set_buffers_user_dimensions(anw.get(), userWidth, userHeight); + if (res != OK) { + ALOGE("%s: Set buffer dimensions failed: %s (%d)", __FUNCTION__, strerror(-res), res); + jniThrowRuntimeException(env, "Set buffer dimensions failed"); + return 0; + } + } + // Query surface format if no valid user format is specified, otherwise, override surface format // with user format. if (userFormat == IMAGE_FORMAT_UNKNOWN) { @@ -1045,7 +1063,7 @@ static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz, static JNINativeMethod gImageWriterMethods[] = { {"nativeClassInit", "()V", (void*)ImageWriter_classInit }, - {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;II)J", + {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIII)J", (void*)ImageWriter_init }, {"nativeClose", "(J)V", (void*)ImageWriter_close }, {"nativeAttachAndQueueImage", "(JJIJIIIIII)I", (void*)ImageWriter_attachAndQueueImage }, diff --git a/media/jni/android_media_JetPlayer.cpp b/media/jni/android_media_JetPlayer.cpp index 481f80b278f8..10a5b586c2b9 100644 --- a/media/jni/android_media_JetPlayer.cpp +++ b/media/jni/android_media_JetPlayer.cpp @@ -43,12 +43,34 @@ struct fields_t { jfieldID nativePlayerInJavaObj; // stores in Java the native JetPlayer object }; -static fields_t javaJetPlayerFields; +static fields_t javaJetPlayerFields {}; +#define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj" +#define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative" + +static void initializeJavaIDs(JNIEnv* env) { + static std::once_flag sJniInitialized; + + std::call_once(sJniInitialized, [](JNIEnv* env) { + // Get the JetPlayer java class + jclass jetPlayerClass = FindClassOrDie(env, kClassPathName); + javaJetPlayerFields.jetClass = MakeGlobalRefOrDie(env, jetPlayerClass); + + // Get the mNativePlayerInJavaObj variable field + javaJetPlayerFields.nativePlayerInJavaObj = + GetFieldIDOrDie(env, jetPlayerClass, JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "J"); + + // Get the callback to post events from this native code to Java + javaJetPlayerFields.postNativeEventInJava = GetStaticMethodIDOrDie(env, + javaJetPlayerFields.jetClass, JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, + "(Ljava/lang/Object;III)V"); + }, env); +} // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- + /* * This function is called from JetPlayer instance's render thread */ @@ -79,6 +101,8 @@ static jboolean android_media_JetPlayer_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint maxTracks, jint trackBufferSize) { + initializeJavaIDs(env); + //ALOGV("android_media_JetPlayer_setup(): entering."); JetPlayer* lpJet = new JetPlayer(env->NewGlobalRef(weak_this), maxTracks, trackBufferSize); @@ -511,28 +535,9 @@ static const JNINativeMethod gMethods[] = { {"native_clearQueue", "()Z", (void *)android_media_JetPlayer_clearQueue}, }; -#define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj" -#define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative" int register_android_media_JetPlayer(JNIEnv *env) { - javaJetPlayerFields.jetClass = NULL; - javaJetPlayerFields.postNativeEventInJava = NULL; - javaJetPlayerFields.nativePlayerInJavaObj = NULL; - - // Get the JetPlayer java class - jclass jetPlayerClass = FindClassOrDie(env, kClassPathName); - javaJetPlayerFields.jetClass = MakeGlobalRefOrDie(env, jetPlayerClass); - - // Get the mNativePlayerInJavaObj variable field - javaJetPlayerFields.nativePlayerInJavaObj = GetFieldIDOrDie(env, - jetPlayerClass, JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "J"); - - // Get the callback to post events from this native code to Java - javaJetPlayerFields.postNativeEventInJava = GetStaticMethodIDOrDie(env, - javaJetPlayerFields.jetClass, JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, - "(Ljava/lang/Object;III)V"); - return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp index 517672ee6127..f491be884b2f 100644 --- a/media/jni/android_media_MediaCrypto.cpp +++ b/media/jni/android_media_MediaCrypto.cpp @@ -202,10 +202,11 @@ static void android_media_MediaCrypto_native_setup( uuid = NULL; if (err != OK) { - jniThrowException( + std::string strerr(StrCryptoError(err)); + jniThrowExceptionFmt( env, "android/media/MediaCryptoException", - "Failed to instantiate crypto object."); + "Failed to instantiate crypto object: %s", strerr.c_str()); return; } @@ -295,7 +296,8 @@ static void android_media_MediaCrypto_setMediaDrmSession( } else if (err == NO_INIT) { msg += ": crypto plugin not initialized"; } else { - msg.appendFormat(": general failure (%d)", err); + std::string strerr(StrCryptoError(err)); + msg.appendFormat(": general failure (%s)", strerr.c_str()); } jniThrowException(env, "android/media/MediaCryptoException", msg.string()); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index eee9f1e08131..b7cde57beaf5 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -217,23 +217,34 @@ namespace android { void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) { ALOGD("LnbClientCallbackImpl::onEvent, type=%d", lnbEventType); JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod( - mLnbObj, - gFields.onLnbEventID, - (jint)lnbEventType); + jobject lnb(env->NewLocalRef(mLnbObj)); + if (!env->IsSameObject(lnb, nullptr)) { + env->CallVoidMethod( + lnb, + gFields.onLnbEventID, + (jint)lnbEventType); + } else { + ALOGE("LnbClientCallbackImpl::onEvent:" + "Lnb object has been freed. Ignoring callback."); + } } void LnbClientCallbackImpl::onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) { ALOGD("LnbClientCallbackImpl::onDiseqcMessage"); JNIEnv *env = AndroidRuntime::getJNIEnv(); - jbyteArray array = env->NewByteArray(diseqcMessage.size()); - env->SetByteArrayRegion( - array, 0, diseqcMessage.size(), reinterpret_cast<jbyte*>(diseqcMessage[0])); - - env->CallVoidMethod( - mLnbObj, - gFields.onLnbDiseqcMessageID, - array); + jobject lnb(env->NewLocalRef(mLnbObj)); + if (!env->IsSameObject(lnb, nullptr)) { + jbyteArray array = env->NewByteArray(diseqcMessage.size()); + env->SetByteArrayRegion( + array, 0, diseqcMessage.size(), reinterpret_cast<jbyte*>(diseqcMessage[0])); + env->CallVoidMethod( + lnb, + gFields.onLnbDiseqcMessageID, + array); + } else { + ALOGE("LnbClientCallbackImpl::onDiseqcMessage:" + "Lnb object has been freed. Ignoring callback."); + } } void LnbClientCallbackImpl::setLnb(jweak lnbObj) { @@ -254,19 +265,31 @@ LnbClientCallbackImpl::~LnbClientCallbackImpl() { void DvrClientCallbackImpl::onRecordStatus(RecordStatus status) { ALOGD("DvrClientCallbackImpl::onRecordStatus"); JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod( - mDvrObj, - gFields.onDvrRecordStatusID, - (jint) status); + jobject dvr(env->NewLocalRef(mDvrObj)); + if (!env->IsSameObject(dvr, nullptr)) { + env->CallVoidMethod( + dvr, + gFields.onDvrRecordStatusID, + (jint) status); + } else { + ALOGE("DvrClientCallbackImpl::onRecordStatus:" + "Dvr object has been freed. Ignoring callback."); + } } void DvrClientCallbackImpl::onPlaybackStatus(PlaybackStatus status) { ALOGD("DvrClientCallbackImpl::onPlaybackStatus"); JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod( - mDvrObj, - gFields.onDvrPlaybackStatusID, - (jint) status); + jobject dvr(env->NewLocalRef(mDvrObj)); + if (!env->IsSameObject(dvr, nullptr)) { + env->CallVoidMethod( + dvr, + gFields.onDvrPlaybackStatusID, + (jint) status); + } else { + ALOGE("DvrClientCallbackImpl::onPlaybackStatus:" + "Dvr object has been freed. Ignoring callback."); + } } void DvrClientCallbackImpl::setDvr(jweak dvrObj) { @@ -538,7 +561,7 @@ jobjectArray FilterClientCallbackImpl::getTsRecordEvent( const std::vector<DemuxFilterEventExt::Event>& eventsExt) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent"); - jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJ)V"); + jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJI)V"); for (int i = 0; i < events.size(); i++) { auto event = events[i]; @@ -591,7 +614,7 @@ jobjectArray FilterClientCallbackImpl::getMmtpRecordEvent( const std::vector<DemuxFilterEventExt::Event>& eventsExt) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent"); - jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJ)V"); + jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJII)V"); for (int i = 0; i < events.size(); i++) { auto event = events[i]; @@ -810,10 +833,16 @@ void FilterClientCallbackImpl::onFilterEvent_1_1(const DemuxFilterEvent& filterE } } } - env->CallVoidMethod( - mFilterObj, - gFields.onFilterEventID, - array); + jobject filter(env->NewLocalRef(mFilterObj)); + if (!env->IsSameObject(filter, nullptr)) { + env->CallVoidMethod( + filter, + gFields.onFilterEventID, + array); + } else { + ALOGE("FilterClientCallbackImpl::onFilterEvent_1_1:" + "Filter object has been freed. Ignoring callback."); + } } void FilterClientCallbackImpl::onFilterEvent(const DemuxFilterEvent& filterEvent) { @@ -828,10 +857,16 @@ void FilterClientCallbackImpl::onFilterEvent(const DemuxFilterEvent& filterEvent void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) { ALOGD("FilterClientCallbackImpl::onFilterStatus"); JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod( - mFilterObj, - gFields.onFilterStatusID, - (jint)status); + jobject filter(env->NewLocalRef(mFilterObj)); + if (!env->IsSameObject(filter, nullptr)) { + env->CallVoidMethod( + filter, + gFields.onFilterStatusID, + (jint)status); + } else { + ALOGE("FilterClientCallbackImpl::onFilterStatus:" + "Filter object has been freed. Ignoring callback."); + } } void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) { @@ -841,6 +876,15 @@ void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filte mFilterClient = filterClient; } +FilterClientCallbackImpl::~FilterClientCallbackImpl() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (mFilterObj != NULL) { + env->DeleteWeakGlobalRef(mFilterObj); + mFilterObj = NULL; + } + mFilterClient = NULL; +} + /////////////// FrontendClientCallbackImpl /////////////////////// FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject(tunerObj) {} @@ -848,10 +892,16 @@ FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) { ALOGD("FrontendClientCallbackImpl::onEvent, type=%d", frontendEventType); JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod( - mObject, - gFields.onFrontendEventID, - (jint)frontendEventType); + jobject frontend(env->NewLocalRef(mObject)); + if (!env->IsSameObject(frontend, nullptr)) { + env->CallVoidMethod( + frontend, + gFields.onFrontendEventID, + (jint)frontendEventType); + } else { + ALOGE("FrontendClientCallbackImpl::onEvent:" + "Frontend object has been freed. Ignoring callback."); + } } void FrontendClientCallbackImpl::onScanMessage( @@ -859,11 +909,17 @@ void FrontendClientCallbackImpl::onScanMessage( ALOGD("FrontendClientCallbackImpl::onScanMessage, type=%d", type); JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass clazz = env->FindClass("android/media/tv/tuner/Tuner"); + jobject frontend(env->NewLocalRef(mObject)); + if (env->IsSameObject(frontend, nullptr)) { + ALOGE("FrontendClientCallbackImpl::onScanMessage:" + "Frontend object has been freed. Ignoring callback."); + return; + } switch(type) { case FrontendScanMessageType::LOCKED: { if (message.isLocked()) { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onLocked", "()V")); } break; @@ -871,14 +927,14 @@ void FrontendClientCallbackImpl::onScanMessage( case FrontendScanMessageType::END: { if (message.isEnd()) { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onScanStopped", "()V")); } break; } case FrontendScanMessageType::PROGRESS_PERCENT: { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onProgress", "(I)V"), (jint) message.progressPercent()); break; @@ -889,7 +945,7 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetIntArrayRegion(freqs, 0, v.size(), reinterpret_cast<jint*>(&v[0])); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([I)V"), freqs); break; @@ -900,21 +956,21 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint*>(&v[0])); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"), symbolRates); break; } case FrontendScanMessageType::HIERARCHY: { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onHierarchy", "(I)V"), (jint) message.hierarchy()); break; } case FrontendScanMessageType::ANALOG_TYPE: { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onSignalType", "(I)V"), (jint) message.analogType()); break; @@ -926,7 +982,7 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetIntArrayRegion(plpIds, 0, jintV.size(), &jintV[0]); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds); break; @@ -938,7 +994,7 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetIntArrayRegion(groupIds, 0, jintV.size(), &jintV[0]); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds); break; @@ -950,7 +1006,7 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetIntArrayRegion(streamIds, 0, jintV.size(), &jintV[0]); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"), streamIds); break; @@ -961,21 +1017,21 @@ void FrontendClientCallbackImpl::onScanMessage( if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::sStd) { standard = (jint) std.sStd(); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onDvbsStandard", "(I)V"), standard); } else if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::tStd) { standard = (jint) std.tStd(); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onDvbtStandard", "(I)V"), standard); } else if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::sifStd) { standard = (jint) std.sifStd(); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onAnalogSifStandard", "(I)V"), standard); } @@ -996,7 +1052,7 @@ void FrontendClientCallbackImpl::onScanMessage( env->SetObjectArrayElement(array, i, obj); } env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onAtsc3PlpInfos", "([Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;)V"), array); @@ -1010,6 +1066,12 @@ void FrontendClientCallbackImpl::onScanMessageExt1_1(FrontendScanMessageTypeExt1 ALOGD("FrontendClientCallbackImpl::onScanMessageExt1_1, type=%d", type); JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass clazz = env->FindClass("android/media/tv/tuner/Tuner"); + jobject frontend(env->NewLocalRef(mObject)); + if (env->IsSameObject(frontend, nullptr)) { + ALOGE("FrontendClientCallbackImpl::onScanMessageExt1_1:" + "Frontend object has been freed. Ignoring callback."); + return; + } switch(type) { case FrontendScanMessageTypeExt1_1::MODULATION: { jint modulation = -1; @@ -1056,7 +1118,7 @@ void FrontendClientCallbackImpl::onScanMessageExt1_1(FrontendScanMessageTypeExt1 } if (modulation > 0) { env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onModulationReported", "(I)V"), modulation); } @@ -1065,7 +1127,7 @@ void FrontendClientCallbackImpl::onScanMessageExt1_1(FrontendScanMessageTypeExt1 case FrontendScanMessageTypeExt1_1::HIGH_PRIORITY: { bool isHighPriority = message.isHighPriority(); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onPriorityReported", "(B)V"), isHighPriority); break; @@ -1073,7 +1135,7 @@ void FrontendClientCallbackImpl::onScanMessageExt1_1(FrontendScanMessageTypeExt1 case FrontendScanMessageTypeExt1_1::DVBC_ANNEX: { jint dvbcAnnex = (jint) message.annex(); env->CallVoidMethod( - mObject, + frontend, env->GetMethodID(clazz, "onDvbcAnnexReported", "(I)V"), dvbcAnnex); break; @@ -1083,6 +1145,14 @@ void FrontendClientCallbackImpl::onScanMessageExt1_1(FrontendScanMessageTypeExt1 } } +FrontendClientCallbackImpl::~FrontendClientCallbackImpl() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (mObject != NULL) { + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + } +} + /////////////// Tuner /////////////////////// sp<TunerClient> JTuner::mTunerClient; @@ -1158,15 +1228,22 @@ jobject JTuner::openFrontendByHandle(int feHandle) { if (mDemuxClient != NULL) { mDemuxClient->setFrontendDataSource(mFeClient); } - sp<FrontendClientCallbackImpl> feClientCb = new FrontendClientCallbackImpl(mObject); - mFeClient->setCallback(feClientCb); JNIEnv *env = AndroidRuntime::getJNIEnv(); + jobject tuner(env->NewLocalRef(mObject)); + if (env->IsSameObject(tuner, nullptr)) { + ALOGE("openFrontendByHandle" + "Tuner object has been freed. Failed to open frontend."); + return NULL; + } + + sp<FrontendClientCallbackImpl> feClientCb = new FrontendClientCallbackImpl(mObject); + mFeClient->setCallback(feClientCb); // TODO: add more fields to frontend return env->NewObject( env->FindClass("android/media/tv/tuner/Tuner$Frontend"), gFields.frontendInitID, - mObject, + tuner, (jint) mFeId); } @@ -1714,16 +1791,14 @@ jobject JTuner::openDvr(DvrType type, jlong bufferSize) { dvrObj = env->NewObject( env->FindClass("android/media/tv/tuner/dvr/DvrRecorder"), - gFields.dvrRecorderInitID, - mObject); + gFields.dvrRecorderInitID); dvrClient->incStrong(dvrObj); env->SetLongField(dvrObj, gFields.dvrRecorderContext, (jlong)dvrClient.get()); } else { dvrObj = env->NewObject( env->FindClass("android/media/tv/tuner/dvr/DvrPlayback"), - gFields.dvrPlaybackInitID, - mObject); + gFields.dvrPlaybackInitID); dvrClient->incStrong(dvrObj); env->SetLongField(dvrObj, gFields.dvrPlaybackContext, (jlong)dvrClient.get()); } @@ -2126,6 +2201,10 @@ jobject JTuner::getFrontendStatus(jintArray types) { intBandwidth = static_cast<jint>(bandwidth.dvbt()); break; } + case FrontendBandwidth::hidl_discriminator::dvbc: { + intBandwidth = static_cast<jint>(bandwidth.dvbc()); + break; + } case FrontendBandwidth::hidl_discriminator::isdbt: { intBandwidth = static_cast<jint>(bandwidth.isdbt()); break; @@ -2646,12 +2725,10 @@ static FrontendSettings getDvbsFrontendSettings(JNIEnv *env, const jobject& sett FrontendDvbsVcmMode vcmMode = static_cast<FrontendDvbsVcmMode>( env->GetIntField(settings, env->GetFieldID(clazz, "mVcmMode", "I"))); - FrontendDvbsCodeRate coderate = getDvbsCodeRate(env, settings); FrontendDvbsSettings frontendDvbsSettings { .frequency = freq, .modulation = modulation, - .coderate = coderate, .symbolRate = symbolRate, .rolloff = rolloff, .pilot = pilot, @@ -2659,6 +2736,13 @@ static FrontendSettings getDvbsFrontendSettings(JNIEnv *env, const jobject& sett .standard = standard, .vcmMode = vcmMode, }; + + jobject jcodeRate = env->GetObjectField(settings, env->GetFieldID(clazz, "mCodeRate", + "Landroid/media/tv/tuner/frontend/DvbsCodeRate;")); + if (jcodeRate != NULL) { + frontendDvbsSettings.coderate = getDvbsCodeRate(env, settings); + } + frontendSettings.dvbs(frontendDvbsSettings); return frontendSettings; } @@ -2670,7 +2754,7 @@ static void getDvbsFrontendSettingsExt1_1(JNIEnv *env, const jobject& settings, static_cast<FrontendDvbsScanType>( env->GetIntField(settings, env->GetFieldID(clazz, "mScanType", "I"))); bool isDiseqcRxMessage = static_cast<bool>(env->GetBooleanField( - settings, env->GetFieldID(clazz, "mIsDiseqcRxMessage", "B"))); + settings, env->GetFieldID(clazz, "mIsDiseqcRxMessage", "Z"))); FrontendDvbsSettingsExt1_1 dvbsExt1_1 { .scanType = scanType, @@ -2910,6 +2994,10 @@ static void getDtmbFrontendSettings(JNIEnv *env, const jobject& settings, static FrontendSettings getFrontendSettings(JNIEnv *env, int type, jobject settings) { ALOGD("getFrontendSettings %d", type); + if (type == static_cast<int>(::android::hardware::tv::tuner::V1_1::FrontendType::DTMB)) { + return FrontendSettings(); + } + FrontendType feType = static_cast<FrontendType>(type); switch(feType) { case FrontendType::ANALOG: @@ -3483,26 +3571,28 @@ static DemuxFilterSettings getFilterConfiguration( .tpid = tpid, }; - DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); - switch (tsType) { - case DemuxTsFilterType::SECTION: - tsFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxTsFilterType::AUDIO: - case DemuxTsFilterType::VIDEO: - tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxTsFilterType::PES: - tsFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxTsFilterType::RECORD: - tsFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - default: - break; + if (settingsObj != NULL) { + DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); + switch (tsType) { + case DemuxTsFilterType::SECTION: + tsFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxTsFilterType::PES: + tsFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxTsFilterType::RECORD: + tsFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.ts(tsFilterSettings); break; @@ -3514,60 +3604,55 @@ static DemuxFilterSettings getFilterConfiguration( DemuxMmtpFilterSettings mmtpFilterSettings { .mmtpPid = mmtpPid, }; - DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); - switch (mmtpType) { - case DemuxMmtpFilterType::SECTION: - mmtpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::AUDIO: - case DemuxMmtpFilterType::VIDEO: - mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::PES: - mmtpFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::RECORD: - mmtpFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::DOWNLOAD: - mmtpFilterSettings.filterSettings.download( - getFilterDownloadSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); + switch (mmtpType) { + case DemuxMmtpFilterType::SECTION: + mmtpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::AUDIO: + case DemuxMmtpFilterType::VIDEO: + mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::PES: + mmtpFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::RECORD: + mmtpFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::DOWNLOAD: + mmtpFilterSettings.filterSettings.download( + getFilterDownloadSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.mmtp(mmtpFilterSettings); break; } case DemuxFilterMainType::IP: { DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj); - DemuxIpFilterSettings ipFilterSettings { .ipAddr = ipAddr, }; + DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype); - switch (ipType) { - case DemuxIpFilterType::SECTION: { - ipFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxIpFilterType::IP: { - jclass clazz = env->FindClass( - "android/media/tv/tuner/filter/IpFilterConfiguration"); - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - ipFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (ipType == DemuxIpFilterType::SECTION && settingsObj != NULL) { + ipFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (ipType == DemuxIpFilterType::IP) { + jclass clazz = env->FindClass( + "android/media/tv/tuner/filter/IpFilterConfiguration"); + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + ipFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.ip(ipFilterSettings); break; @@ -3584,24 +3669,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .isCompressedIpPacket = isCompressedIpPacket, }; + DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype); - switch (tlvType) { - case DemuxTlvFilterType::SECTION: { - tlvFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxTlvFilterType::TLV: { - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (tlvType == DemuxTlvFilterType::SECTION && settingsObj != NULL) { + tlvFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (tlvType == DemuxTlvFilterType::TLV) { + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.tlv(tlvFilterSettings); break; @@ -3616,14 +3694,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .lengthType = lengthType, }; - DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); - switch (alpType) { - case DemuxAlpFilterType::SECTION: - alpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); + switch (alpType) { + case DemuxAlpFilterType::SECTION: + alpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.alp(alpFilterSettings); break; diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 0e30b18eb2d4..fafef4221541 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -118,6 +118,7 @@ struct MediaEvent : public RefBase { }; struct FilterClientCallbackImpl : public FilterClientCallback { + ~FilterClientCallbackImpl(); virtual void onFilterEvent_1_1(const DemuxFilterEvent& filterEvent, const DemuxFilterEventExt& filterEventExt); virtual void onFilterEvent(const DemuxFilterEvent& filterEvent); @@ -155,7 +156,7 @@ private: struct FrontendClientCallbackImpl : public FrontendClientCallback { FrontendClientCallbackImpl(jweak tunerObj); - + ~FrontendClientCallbackImpl(); virtual void onEvent(FrontendEventType frontendEventType); virtual void onScanMessage( FrontendScanMessageType type, const FrontendScanMessage& message); diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index 4efdcaccf7a1..ffed4747d3ea 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -32,6 +32,7 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> +#include "core_jni_helpers.h" #include <jni.h> #include <media/stagefright/NuMediaExtractor.h> #include <nativehelper/JNIHelp.h> @@ -48,6 +49,7 @@ using namespace android; // ---------------------------------------------------------------------------- +// MtpDatabase methods static jmethodID method_beginSendObject; static jmethodID method_endSendObject; static jmethodID method_rescanFile; @@ -75,6 +77,7 @@ static jmethodID method_endCopyObject; static jmethodID method_getObjectReferences; static jmethodID method_setObjectReferences; +// MtpDatabase fields. static jfieldID field_context; // MtpPropertyList methods @@ -86,6 +89,59 @@ static jmethodID method_getDataTypes; static jmethodID method_getLongValues; static jmethodID method_getStringValues; +// Initializer for the jfieldIDs and jmethodIDs above. This method must be invoked +// before using these static fields and methods for JNI accesses. +static void initializeJavaIDs(JNIEnv* env) { + static std::once_flag sJniInitialized; + +#define GET_METHOD_ID(name, jclass, signature) \ + method_##name = GetMethodIDOrDie(env, jclass, #name, signature); + + std::call_once(sJniInitialized, [](JNIEnv* env) { + const jclass mdb_class = FindClassOrDie(env, "android/mtp/MtpDatabase"); + GET_METHOD_ID(beginSendObject, mdb_class, "(Ljava/lang/String;III)I"); + GET_METHOD_ID(endSendObject, mdb_class, "(IZ)V"); + GET_METHOD_ID(rescanFile, mdb_class, "(Ljava/lang/String;II)V"); + GET_METHOD_ID(getObjectList, mdb_class, "(III)[I"); + GET_METHOD_ID(getNumObjects, mdb_class, "(III)I"); + GET_METHOD_ID(getSupportedPlaybackFormats, mdb_class, "()[I"); + GET_METHOD_ID(getSupportedCaptureFormats, mdb_class, "()[I"); + GET_METHOD_ID(getSupportedObjectProperties, mdb_class, "(I)[I"); + GET_METHOD_ID(getSupportedDeviceProperties, mdb_class, "()[I"); + GET_METHOD_ID(setObjectProperty, mdb_class, "(IIJLjava/lang/String;)I"); + GET_METHOD_ID(getDeviceProperty, mdb_class, "(I[J[C)I"); + GET_METHOD_ID(setDeviceProperty, mdb_class, "(IJLjava/lang/String;)I"); + GET_METHOD_ID(getObjectPropertyList, mdb_class, "(IIIII)Landroid/mtp/MtpPropertyList;"); + GET_METHOD_ID(getObjectInfo, mdb_class, "(I[I[C[J)Z"); + GET_METHOD_ID(getObjectFilePath, mdb_class, "(I[C[J)I"); + GET_METHOD_ID(openFilePath, mdb_class, "(Ljava/lang/String;Z)I"); + GET_METHOD_ID(getThumbnailInfo, mdb_class, "(I[J)Z"); + GET_METHOD_ID(getThumbnailData, mdb_class, "(I)[B"); + GET_METHOD_ID(beginDeleteObject, mdb_class, "(I)I"); + GET_METHOD_ID(endDeleteObject, mdb_class, "(IZ)V"); + GET_METHOD_ID(beginMoveObject, mdb_class, "(III)I"); + GET_METHOD_ID(endMoveObject, mdb_class, "(IIIIIZ)V"); + GET_METHOD_ID(beginCopyObject, mdb_class, "(III)I"); + GET_METHOD_ID(endCopyObject, mdb_class, "(IZ)V"); + GET_METHOD_ID(getObjectReferences, mdb_class, "(I)[I"); + GET_METHOD_ID(setObjectReferences, mdb_class, "(I[I)I"); + field_context = GetFieldIDOrDie(env, mdb_class, "mNativeContext", "J"); + + const jclass mpl_class = FindClassOrDie(env, "android/mtp/MtpPropertyList"); + GET_METHOD_ID(getCode, mpl_class, "()I"); + GET_METHOD_ID(getCount, mpl_class, "()I"); + GET_METHOD_ID(getObjectHandles, mpl_class, "()[I"); + GET_METHOD_ID(getPropertyCodes, mpl_class, "()[I"); + GET_METHOD_ID(getDataTypes, mpl_class, "()[I"); + GET_METHOD_ID(getLongValues, mpl_class, "()[J"); + GET_METHOD_ID(getStringValues, mpl_class, "()[Ljava/lang/String;"); + + return 0; + }, env); + +#undef GET_METHOD_ID +} + IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) { return (IMtpDatabase *)env->GetLongField(database, field_context); @@ -1280,6 +1336,7 @@ MtpProperty* MtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) { static void android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz) { + initializeJavaIDs(env); MtpDatabase* database = new MtpDatabase(env, thiz); env->SetLongField(thiz, field_context, (jlong)database); checkAndClearExceptionFromCallback(env, __FUNCTION__); @@ -1314,69 +1371,9 @@ static const JNINativeMethod gMtpPropertyGroupMethods[] = { {"format_date_time", "(J)Ljava/lang/String;", (void *)android_mtp_MtpPropertyGroup_format_date_time}, }; - -#define GET_METHOD_ID(name, jclass, signature) \ - method_##name = env->GetMethodID(jclass, #name, signature); \ - if (method_##name == NULL) { \ - ALOGE("Can't find " #name); \ - return -1; \ - } \ - + \ int register_android_mtp_MtpDatabase(JNIEnv *env) { - jclass clazz; - - clazz = env->FindClass("android/mtp/MtpDatabase"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpDatabase"); - return -1; - } - GET_METHOD_ID(beginSendObject, clazz, "(Ljava/lang/String;III)I"); - GET_METHOD_ID(endSendObject, clazz, "(IZ)V"); - GET_METHOD_ID(rescanFile, clazz, "(Ljava/lang/String;II)V"); - GET_METHOD_ID(getObjectList, clazz, "(III)[I"); - GET_METHOD_ID(getNumObjects, clazz, "(III)I"); - GET_METHOD_ID(getSupportedPlaybackFormats, clazz, "()[I"); - GET_METHOD_ID(getSupportedCaptureFormats, clazz, "()[I"); - GET_METHOD_ID(getSupportedObjectProperties, clazz, "(I)[I"); - GET_METHOD_ID(getSupportedDeviceProperties, clazz, "()[I"); - GET_METHOD_ID(setObjectProperty, clazz, "(IIJLjava/lang/String;)I"); - GET_METHOD_ID(getDeviceProperty, clazz, "(I[J[C)I"); - GET_METHOD_ID(setDeviceProperty, clazz, "(IJLjava/lang/String;)I"); - GET_METHOD_ID(getObjectPropertyList, clazz, "(IIIII)Landroid/mtp/MtpPropertyList;"); - GET_METHOD_ID(getObjectInfo, clazz, "(I[I[C[J)Z"); - GET_METHOD_ID(getObjectFilePath, clazz, "(I[C[J)I"); - GET_METHOD_ID(openFilePath, clazz, "(Ljava/lang/String;Z)I"); - GET_METHOD_ID(getThumbnailInfo, clazz, "(I[J)Z"); - GET_METHOD_ID(getThumbnailData, clazz, "(I)[B"); - GET_METHOD_ID(beginDeleteObject, clazz, "(I)I"); - GET_METHOD_ID(endDeleteObject, clazz, "(IZ)V"); - GET_METHOD_ID(beginMoveObject, clazz, "(III)I"); - GET_METHOD_ID(endMoveObject, clazz, "(IIIIIZ)V"); - GET_METHOD_ID(beginCopyObject, clazz, "(III)I"); - GET_METHOD_ID(endCopyObject, clazz, "(IZ)V"); - GET_METHOD_ID(getObjectReferences, clazz, "(I)[I"); - GET_METHOD_ID(setObjectReferences, clazz, "(I[I)I"); - - field_context = env->GetFieldID(clazz, "mNativeContext", "J"); - if (field_context == NULL) { - ALOGE("Can't find MtpDatabase.mNativeContext"); - return -1; - } - - clazz = env->FindClass("android/mtp/MtpPropertyList"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpPropertyList"); - return -1; - } - GET_METHOD_ID(getCode, clazz, "()I"); - GET_METHOD_ID(getCount, clazz, "()I"); - GET_METHOD_ID(getObjectHandles, clazz, "()[I"); - GET_METHOD_ID(getPropertyCodes, clazz, "()[I"); - GET_METHOD_ID(getDataTypes, clazz, "()[I"); - GET_METHOD_ID(getLongValues, clazz, "()[J"); - GET_METHOD_ID(getStringValues, clazz, "()[Ljava/lang/String;"); - if (AndroidRuntime::registerNativeMethods(env, "android/mtp/MtpDatabase", gMtpDatabaseMethods, NELEM(gMtpDatabaseMethods))) return -1; diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp index 060eaf9ccad4..3d2b00fec26c 100644 --- a/media/jni/android_mtp_MtpDevice.cpp +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -34,6 +34,7 @@ #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" +#include "core_jni_helpers.h" #include "nativehelper/ScopedLocalRef.h" #include "private/android_filesystem_config.h" @@ -107,6 +108,95 @@ static jfieldID field_event_parameter1; static jfieldID field_event_parameter2; static jfieldID field_event_parameter3; +// Initializer for the jclasses, jfieldIDs and jmethodIDs declared above. This method must be +// invoked before using these static fields for JNI accesses. +static void initializeJavaIDs(JNIEnv* env) { + static std::once_flag sJniInitialized; + + std::call_once(sJniInitialized, [](JNIEnv* env) { + clazz_deviceInfo = + (jclass)env->NewGlobalRef(FindClassOrDie(env, "android/mtp/MtpDeviceInfo")); + constructor_deviceInfo = GetMethodIDOrDie(env, clazz_deviceInfo, "<init>", "()V"); + field_deviceInfo_manufacturer = + GetFieldIDOrDie(env, clazz_deviceInfo, "mManufacturer", "Ljava/lang/String;"); + field_deviceInfo_model = + GetFieldIDOrDie(env, clazz_deviceInfo, "mModel", "Ljava/lang/String;"); + field_deviceInfo_version = + GetFieldIDOrDie(env, clazz_deviceInfo, "mVersion", "Ljava/lang/String;"); + field_deviceInfo_serialNumber = + GetFieldIDOrDie(env, clazz_deviceInfo, "mSerialNumber", "Ljava/lang/String;"); + field_deviceInfo_operationsSupported = + GetFieldIDOrDie(env, clazz_deviceInfo, "mOperationsSupported", "[I"); + field_deviceInfo_eventsSupported = + GetFieldIDOrDie(env, clazz_deviceInfo, "mEventsSupported", "[I"); + + clazz_storageInfo = + (jclass)env->NewGlobalRef(FindClassOrDie(env, "android/mtp/MtpStorageInfo")); + constructor_storageInfo = GetMethodIDOrDie(env, clazz_storageInfo, "<init>", "()V"); + field_storageInfo_storageId = GetFieldIDOrDie(env, clazz_storageInfo, "mStorageId", "I"); + field_storageInfo_maxCapacity = + GetFieldIDOrDie(env, clazz_storageInfo, "mMaxCapacity", "J"); + field_storageInfo_freeSpace = + GetFieldIDOrDie(env, clazz_storageInfo, "mFreeSpace", "J"); + field_storageInfo_description = + GetFieldIDOrDie(env, clazz_storageInfo, "mDescription", "Ljava/lang/String;"); + field_storageInfo_volumeIdentifier = + GetFieldIDOrDie(env, clazz_storageInfo, "mVolumeIdentifier", "Ljava/lang/String;"); + + clazz_objectInfo = + (jclass)env->NewGlobalRef(FindClassOrDie(env, "android/mtp/MtpObjectInfo")); + constructor_objectInfo = GetMethodIDOrDie(env, clazz_objectInfo, "<init>", "()V"); + field_objectInfo_handle = GetFieldIDOrDie(env, clazz_objectInfo, "mHandle", "I"); + field_objectInfo_storageId = GetFieldIDOrDie(env, clazz_objectInfo, "mStorageId", "I"); + field_objectInfo_format = GetFieldIDOrDie(env, clazz_objectInfo, "mFormat", "I"); + field_objectInfo_protectionStatus = + GetFieldIDOrDie(env, clazz_objectInfo, "mProtectionStatus", "I"); + field_objectInfo_compressedSize = + GetFieldIDOrDie(env, clazz_objectInfo, "mCompressedSize", "I"); + field_objectInfo_thumbFormat = GetFieldIDOrDie(env, clazz_objectInfo, "mThumbFormat", "I"); + field_objectInfo_thumbCompressedSize = + GetFieldIDOrDie(env, clazz_objectInfo, "mThumbCompressedSize", "I"); + field_objectInfo_thumbPixWidth = + GetFieldIDOrDie(env, clazz_objectInfo, "mThumbPixWidth", "I"); + field_objectInfo_thumbPixHeight = + GetFieldIDOrDie(env, clazz_objectInfo, "mThumbPixHeight", "I"); + field_objectInfo_imagePixWidth = + GetFieldIDOrDie(env, clazz_objectInfo, "mImagePixWidth", "I"); + field_objectInfo_imagePixHeight = + GetFieldIDOrDie(env, clazz_objectInfo, "mImagePixHeight", "I"); + field_objectInfo_imagePixDepth = + GetFieldIDOrDie(env, clazz_objectInfo, "mImagePixDepth", "I"); + field_objectInfo_parent = GetFieldIDOrDie(env, clazz_objectInfo, "mParent", "I"); + field_objectInfo_associationType = + GetFieldIDOrDie(env, clazz_objectInfo, "mAssociationType", "I"); + field_objectInfo_associationDesc = + GetFieldIDOrDie(env, clazz_objectInfo, "mAssociationDesc", "I"); + field_objectInfo_sequenceNumber = + GetFieldIDOrDie(env, clazz_objectInfo, "mSequenceNumber", "I"); + field_objectInfo_name = + GetFieldIDOrDie(env, clazz_objectInfo, "mName", "Ljava/lang/String;"); + field_objectInfo_dateCreated = GetFieldIDOrDie(env, clazz_objectInfo, "mDateCreated", "J"); + field_objectInfo_dateModified = + GetFieldIDOrDie(env, clazz_objectInfo, "mDateModified", "J"); + field_objectInfo_keywords = + GetFieldIDOrDie(env, clazz_objectInfo, "mKeywords", "Ljava/lang/String;"); + + clazz_event = (jclass)env->NewGlobalRef(FindClassOrDie(env, "android/mtp/MtpEvent")); + constructor_event = GetMethodIDOrDie(env, clazz_event, "<init>", "()V"); + field_event_eventCode = GetFieldIDOrDie(env, clazz_event, "mEventCode", "I"); + field_event_parameter1 = GetFieldIDOrDie(env, clazz_event, "mParameter1", "I"); + field_event_parameter2 = GetFieldIDOrDie(env, clazz_event, "mParameter2", "I"); + field_event_parameter3 = GetFieldIDOrDie(env, clazz_event, "mParameter3", "I"); + + const jclass clazz = FindClassOrDie(env, "android/mtp/MtpDevice"); + field_context = GetFieldIDOrDie(env, clazz, "mNativeContext", "J"); + + clazz_io_exception = (jclass)env->NewGlobalRef(FindClassOrDie(env, "java/io/IOException")); + clazz_operation_canceled_exception = + (jclass)env->NewGlobalRef(FindClassOrDie(env, "android/os/OperationCanceledException")); + }, env); +} + class JavaArrayWriter { public: JavaArrayWriter(JNIEnv* env, jbyteArray array) : @@ -132,6 +222,11 @@ private: MtpDevice* get_device_from_object(JNIEnv* env, jobject javaDevice) { + // get_device_from_object() is called by the majority of JNI methods in this file. Call + // `initializeJavaIDs()` here to ensure JNI methodID's, fieldIDs and classes are initialized + // before use. + initializeJavaIDs(env); + return (MtpDevice*)env->GetLongField(javaDevice, field_context); } @@ -200,6 +295,8 @@ android_mtp_MtpDevice_open(JNIEnv *env, jobject thiz, jstring deviceName, jint f MtpDevice* device = MtpDevice::open(deviceNameStr, fd); env->ReleaseStringUTFChars(deviceName, deviceNameStr); + // The jfieldID `field_context` needs to be initialized before use below. + initializeJavaIDs(env); if (device) env->SetLongField(thiz, field_context, (jlong)device); return (jboolean)(device != NULL); @@ -781,256 +878,7 @@ static const JNINativeMethod gMethods[] = { int register_android_mtp_MtpDevice(JNIEnv *env) { - jclass clazz; - ALOGD("register_android_mtp_MtpDevice\n"); - - clazz = env->FindClass("android/mtp/MtpDeviceInfo"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpDeviceInfo"); - return -1; - } - constructor_deviceInfo = env->GetMethodID(clazz, "<init>", "()V"); - if (constructor_deviceInfo == NULL) { - ALOGE("Can't find android/mtp/MtpDeviceInfo constructor"); - return -1; - } - field_deviceInfo_manufacturer = env->GetFieldID(clazz, "mManufacturer", "Ljava/lang/String;"); - if (field_deviceInfo_manufacturer == NULL) { - ALOGE("Can't find MtpDeviceInfo.mManufacturer"); - return -1; - } - field_deviceInfo_model = env->GetFieldID(clazz, "mModel", "Ljava/lang/String;"); - if (field_deviceInfo_model == NULL) { - ALOGE("Can't find MtpDeviceInfo.mModel"); - return -1; - } - field_deviceInfo_version = env->GetFieldID(clazz, "mVersion", "Ljava/lang/String;"); - if (field_deviceInfo_version == NULL) { - ALOGE("Can't find MtpDeviceInfo.mVersion"); - return -1; - } - field_deviceInfo_serialNumber = env->GetFieldID(clazz, "mSerialNumber", "Ljava/lang/String;"); - if (field_deviceInfo_serialNumber == NULL) { - ALOGE("Can't find MtpDeviceInfo.mSerialNumber"); - return -1; - } - field_deviceInfo_operationsSupported = env->GetFieldID(clazz, "mOperationsSupported", "[I"); - if (field_deviceInfo_operationsSupported == NULL) { - ALOGE("Can't find MtpDeviceInfo.mOperationsSupported"); - return -1; - } - field_deviceInfo_eventsSupported = env->GetFieldID(clazz, "mEventsSupported", "[I"); - if (field_deviceInfo_eventsSupported == NULL) { - ALOGE("Can't find MtpDeviceInfo.mEventsSupported"); - return -1; - } - clazz_deviceInfo = (jclass)env->NewGlobalRef(clazz); - - clazz = env->FindClass("android/mtp/MtpStorageInfo"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpStorageInfo"); - return -1; - } - constructor_storageInfo = env->GetMethodID(clazz, "<init>", "()V"); - if (constructor_storageInfo == NULL) { - ALOGE("Can't find android/mtp/MtpStorageInfo constructor"); - return -1; - } - field_storageInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I"); - if (field_storageInfo_storageId == NULL) { - ALOGE("Can't find MtpStorageInfo.mStorageId"); - return -1; - } - field_storageInfo_maxCapacity = env->GetFieldID(clazz, "mMaxCapacity", "J"); - if (field_storageInfo_maxCapacity == NULL) { - ALOGE("Can't find MtpStorageInfo.mMaxCapacity"); - return -1; - } - field_storageInfo_freeSpace = env->GetFieldID(clazz, "mFreeSpace", "J"); - if (field_storageInfo_freeSpace == NULL) { - ALOGE("Can't find MtpStorageInfo.mFreeSpace"); - return -1; - } - field_storageInfo_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;"); - if (field_storageInfo_description == NULL) { - ALOGE("Can't find MtpStorageInfo.mDescription"); - return -1; - } - field_storageInfo_volumeIdentifier = env->GetFieldID(clazz, "mVolumeIdentifier", "Ljava/lang/String;"); - if (field_storageInfo_volumeIdentifier == NULL) { - ALOGE("Can't find MtpStorageInfo.mVolumeIdentifier"); - return -1; - } - clazz_storageInfo = (jclass)env->NewGlobalRef(clazz); - - clazz = env->FindClass("android/mtp/MtpObjectInfo"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpObjectInfo"); - return -1; - } - constructor_objectInfo = env->GetMethodID(clazz, "<init>", "()V"); - if (constructor_objectInfo == NULL) { - ALOGE("Can't find android/mtp/MtpObjectInfo constructor"); - return -1; - } - field_objectInfo_handle = env->GetFieldID(clazz, "mHandle", "I"); - if (field_objectInfo_handle == NULL) { - ALOGE("Can't find MtpObjectInfo.mHandle"); - return -1; - } - field_objectInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I"); - if (field_objectInfo_storageId == NULL) { - ALOGE("Can't find MtpObjectInfo.mStorageId"); - return -1; - } - field_objectInfo_format = env->GetFieldID(clazz, "mFormat", "I"); - if (field_objectInfo_format == NULL) { - ALOGE("Can't find MtpObjectInfo.mFormat"); - return -1; - } - field_objectInfo_protectionStatus = env->GetFieldID(clazz, "mProtectionStatus", "I"); - if (field_objectInfo_protectionStatus == NULL) { - ALOGE("Can't find MtpObjectInfo.mProtectionStatus"); - return -1; - } - field_objectInfo_compressedSize = env->GetFieldID(clazz, "mCompressedSize", "I"); - if (field_objectInfo_compressedSize == NULL) { - ALOGE("Can't find MtpObjectInfo.mCompressedSize"); - return -1; - } - field_objectInfo_thumbFormat = env->GetFieldID(clazz, "mThumbFormat", "I"); - if (field_objectInfo_thumbFormat == NULL) { - ALOGE("Can't find MtpObjectInfo.mThumbFormat"); - return -1; - } - field_objectInfo_thumbCompressedSize = env->GetFieldID(clazz, "mThumbCompressedSize", "I"); - if (field_objectInfo_thumbCompressedSize == NULL) { - ALOGE("Can't find MtpObjectInfo.mThumbCompressedSize"); - return -1; - } - field_objectInfo_thumbPixWidth = env->GetFieldID(clazz, "mThumbPixWidth", "I"); - if (field_objectInfo_thumbPixWidth == NULL) { - ALOGE("Can't find MtpObjectInfo.mThumbPixWidth"); - return -1; - } - field_objectInfo_thumbPixHeight = env->GetFieldID(clazz, "mThumbPixHeight", "I"); - if (field_objectInfo_thumbPixHeight == NULL) { - ALOGE("Can't find MtpObjectInfo.mThumbPixHeight"); - return -1; - } - field_objectInfo_imagePixWidth = env->GetFieldID(clazz, "mImagePixWidth", "I"); - if (field_objectInfo_imagePixWidth == NULL) { - ALOGE("Can't find MtpObjectInfo.mImagePixWidth"); - return -1; - } - field_objectInfo_imagePixHeight = env->GetFieldID(clazz, "mImagePixHeight", "I"); - if (field_objectInfo_imagePixHeight == NULL) { - ALOGE("Can't find MtpObjectInfo.mImagePixHeight"); - return -1; - } - field_objectInfo_imagePixDepth = env->GetFieldID(clazz, "mImagePixDepth", "I"); - if (field_objectInfo_imagePixDepth == NULL) { - ALOGE("Can't find MtpObjectInfo.mImagePixDepth"); - return -1; - } - field_objectInfo_parent = env->GetFieldID(clazz, "mParent", "I"); - if (field_objectInfo_parent == NULL) { - ALOGE("Can't find MtpObjectInfo.mParent"); - return -1; - } - field_objectInfo_associationType = env->GetFieldID(clazz, "mAssociationType", "I"); - if (field_objectInfo_associationType == NULL) { - ALOGE("Can't find MtpObjectInfo.mAssociationType"); - return -1; - } - field_objectInfo_associationDesc = env->GetFieldID(clazz, "mAssociationDesc", "I"); - if (field_objectInfo_associationDesc == NULL) { - ALOGE("Can't find MtpObjectInfo.mAssociationDesc"); - return -1; - } - field_objectInfo_sequenceNumber = env->GetFieldID(clazz, "mSequenceNumber", "I"); - if (field_objectInfo_sequenceNumber == NULL) { - ALOGE("Can't find MtpObjectInfo.mSequenceNumber"); - return -1; - } - field_objectInfo_name = env->GetFieldID(clazz, "mName", "Ljava/lang/String;"); - if (field_objectInfo_name == NULL) { - ALOGE("Can't find MtpObjectInfo.mName"); - return -1; - } - field_objectInfo_dateCreated = env->GetFieldID(clazz, "mDateCreated", "J"); - if (field_objectInfo_dateCreated == NULL) { - ALOGE("Can't find MtpObjectInfo.mDateCreated"); - return -1; - } - field_objectInfo_dateModified = env->GetFieldID(clazz, "mDateModified", "J"); - if (field_objectInfo_dateModified == NULL) { - ALOGE("Can't find MtpObjectInfo.mDateModified"); - return -1; - } - field_objectInfo_keywords = env->GetFieldID(clazz, "mKeywords", "Ljava/lang/String;"); - if (field_objectInfo_keywords == NULL) { - ALOGE("Can't find MtpObjectInfo.mKeywords"); - return -1; - } - clazz_objectInfo = (jclass)env->NewGlobalRef(clazz); - - clazz = env->FindClass("android/mtp/MtpEvent"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpEvent"); - return -1; - } - constructor_event = env->GetMethodID(clazz, "<init>", "()V"); - if (constructor_event == NULL) { - ALOGE("Can't find android/mtp/MtpEvent constructor"); - return -1; - } - field_event_eventCode = env->GetFieldID(clazz, "mEventCode", "I"); - if (field_event_eventCode == NULL) { - ALOGE("Can't find MtpObjectInfo.mEventCode"); - return -1; - } - field_event_parameter1 = env->GetFieldID(clazz, "mParameter1", "I"); - if (field_event_parameter1 == NULL) { - ALOGE("Can't find MtpObjectInfo.mParameter1"); - return -1; - } - field_event_parameter2 = env->GetFieldID(clazz, "mParameter2", "I"); - if (field_event_parameter2 == NULL) { - ALOGE("Can't find MtpObjectInfo.mParameter2"); - return -1; - } - field_event_parameter3 = env->GetFieldID(clazz, "mParameter3", "I"); - if (field_event_parameter3 == NULL) { - ALOGE("Can't find MtpObjectInfo.mParameter3"); - return -1; - } - clazz_event = (jclass)env->NewGlobalRef(clazz); - - clazz = env->FindClass("android/mtp/MtpDevice"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpDevice"); - return -1; - } - field_context = env->GetFieldID(clazz, "mNativeContext", "J"); - if (field_context == NULL) { - ALOGE("Can't find MtpDevice.mNativeContext"); - return -1; - } - clazz = env->FindClass("java/io/IOException"); - if (clazz == NULL) { - ALOGE("Can't find java.io.IOException"); - return -1; - } - clazz_io_exception = (jclass)env->NewGlobalRef(clazz); - clazz = env->FindClass("android/os/OperationCanceledException"); - if (clazz == NULL) { - ALOGE("Can't find android.os.OperationCanceledException"); - return -1; - } - clazz_operation_canceled_exception = (jclass)env->NewGlobalRef(clazz); - return AndroidRuntime::registerNativeMethods(env, "android/mtp/MtpDevice", gMethods, NELEM(gMethods)); } diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp index 8a1ae9252ca3..b4844f79b540 100644 --- a/media/jni/android_mtp_MtpServer.cpp +++ b/media/jni/android_mtp_MtpServer.cpp @@ -24,6 +24,7 @@ #include <fcntl.h> #include <utils/threads.h> +#include "core_jni_helpers.h" #include "jni.h" #include <nativehelper/JNIPlatformHelp.h> #include "android_runtime/AndroidRuntime.h" @@ -34,6 +35,8 @@ using namespace android; +static Mutex sMutex; + // MtpServer fields static jfieldID field_MtpServer_nativeContext; @@ -44,7 +47,25 @@ static jfieldID field_MtpStorage_description; static jfieldID field_MtpStorage_removable; static jfieldID field_MtpStorage_maxFileSize; -static Mutex sMutex; +// Initializer for the jfieldIDs above. This method must be invoked before accessing MtpServer and +// MtpStorage fields. +static void initializeJavaIDs(JNIEnv* env) { + static std::once_flag sJniInitialized; + + std::call_once(sJniInitialized, [](JNIEnv *env) { + const jclass storage_clazz = FindClassOrDie(env, "android/mtp/MtpStorage"); + field_MtpStorage_storageId = GetFieldIDOrDie(env, storage_clazz, "mStorageId", "I"); + field_MtpStorage_path = + GetFieldIDOrDie(env, storage_clazz, "mPath", "Ljava/lang/String;"); + field_MtpStorage_description = + GetFieldIDOrDie(env, storage_clazz, "mDescription", "Ljava/lang/String;"); + field_MtpStorage_removable = GetFieldIDOrDie(env, storage_clazz, "mRemovable", "Z"); + field_MtpStorage_maxFileSize = GetFieldIDOrDie(env, storage_clazz, "mMaxFileSize", "J"); + + const jclass server_clazz = FindClassOrDie(env, "android/mtp/MtpServer"); + field_MtpServer_nativeContext = GetFieldIDOrDie(env, server_clazz, "mNativeContext", "J"); + }, env); +} // ---------------------------------------------------------------------------- @@ -52,6 +73,7 @@ static Mutex sMutex; extern IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database); static inline MtpServer* getMtpServer(JNIEnv *env, jobject thiz) { + initializeJavaIDs(env); return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext); } @@ -60,6 +82,8 @@ android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, job jboolean usePtp, jstring deviceInfoManufacturer, jstring deviceInfoModel, jstring deviceInfoDeviceVersion, jstring deviceInfoSerialNumber) { + initializeJavaIDs(env); + const char *deviceInfoManufacturerStr = env->GetStringUTFChars(deviceInfoManufacturer, NULL); const char *deviceInfoModelStr = env->GetStringUTFChars(deviceInfoModel, NULL); const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL); @@ -224,50 +248,6 @@ static const JNINativeMethod gMethods[] = { int register_android_mtp_MtpServer(JNIEnv *env) { - jclass clazz; - - clazz = env->FindClass("android/mtp/MtpStorage"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpStorage"); - return -1; - } - field_MtpStorage_storageId = env->GetFieldID(clazz, "mStorageId", "I"); - if (field_MtpStorage_storageId == NULL) { - ALOGE("Can't find MtpStorage.mStorageId"); - return -1; - } - field_MtpStorage_path = env->GetFieldID(clazz, "mPath", "Ljava/lang/String;"); - if (field_MtpStorage_path == NULL) { - ALOGE("Can't find MtpStorage.mPath"); - return -1; - } - field_MtpStorage_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;"); - if (field_MtpStorage_description == NULL) { - ALOGE("Can't find MtpStorage.mDescription"); - return -1; - } - field_MtpStorage_removable = env->GetFieldID(clazz, "mRemovable", "Z"); - if (field_MtpStorage_removable == NULL) { - ALOGE("Can't find MtpStorage.mRemovable"); - return -1; - } - field_MtpStorage_maxFileSize = env->GetFieldID(clazz, "mMaxFileSize", "J"); - if (field_MtpStorage_maxFileSize == NULL) { - ALOGE("Can't find MtpStorage.mMaxFileSize"); - return -1; - } - - clazz = env->FindClass("android/mtp/MtpServer"); - if (clazz == NULL) { - ALOGE("Can't find android/mtp/MtpServer"); - return -1; - } - field_MtpServer_nativeContext = env->GetFieldID(clazz, "mNativeContext", "J"); - if (field_MtpServer_nativeContext == NULL) { - ALOGE("Can't find MtpServer.mNativeContext"); - return -1; - } - return AndroidRuntime::registerNativeMethods(env, "android/mtp/MtpServer", gMethods, NELEM(gMethods)); } diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp index 7b498e027d1c..b3406cd89046 100644 --- a/media/jni/soundpool/Android.bp +++ b/media/jni/soundpool/Android.bp @@ -45,26 +45,30 @@ tidy_errors = [ "modernize-return-braced-init-list", "modernize-shrink-to-fit", "modernize-unary-static-assert", - "modernize-use-auto", // debatable - auto can obscure type + // "modernize-use-auto", // found in StreamManager.h, debatable - auto can obscure type "modernize-use-bool-literals", "modernize-use-default-member-init", "modernize-use-emplace", "modernize-use-equals-default", "modernize-use-equals-delete", - "modernize-use-nodiscard", + // "modernize-use-nodiscard", // found in SteamManager.h "modernize-use-noexcept", "modernize-use-nullptr", "modernize-use-override", //"modernize-use-trailing-return-type", // not necessarily more readable "modernize-use-transparent-functors", "modernize-use-uncaught-exceptions", - "modernize-use-using", + //"modernize-use-using", // found in SoundManager.h "performance-*", // Remove some pedantic stylistic requirements. "-google-readability-casting", // C++ casts not always necessary and may be verbose "-google-readability-todo", // do not require TODO(info) "-google-build-using-namespace", // Reenable and fix later. + + "-google-explicit-constructor", // found in StreamManager.h + "-misc-non-private-member-variables-in-classes", // found in SoundManager.h + "-performance-unnecessary-value-param", // found in StreamManager.h ] cc_defaults { @@ -96,8 +100,7 @@ cc_defaults { tidy_checks: tidy_errors, tidy_checks_as_errors: tidy_errors, tidy_flags: [ - "-format-style='file'", - "--header-filter='frameworks/base/media/jni/soundpool'", + "-format-style=file", ], } diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index f400a7157051..f54e2663843c 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -588,14 +588,15 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( break; } case TunerFrontendStatus::codeRates: { - int size = s.get<TunerFrontendStatus::codeRates>().size(); - status.codeRates().resize(size); - for (int i = 0; i < size; i++) { - auto aidlCodeRate = s.get<TunerFrontendStatus::codeRates>()[i]; - status.codeRates()[i] = - static_cast<hardware::tv::tuner::V1_1::FrontendInnerFec>(aidlCodeRate); + vector<hardware::tv::tuner::V1_1::FrontendInnerFec> codeRates; + for (auto aidlCodeRate : s.get<TunerFrontendStatus::codeRates>()) { + codeRates.push_back( + static_cast<hardware::tv::tuner::V1_1::FrontendInnerFec>(aidlCodeRate)); + } + if (codeRates.size() > 0) { + status.codeRates(codeRates); + hidlStatus.push_back(status); } - hidlStatus.push_back(status); break; } case TunerFrontendStatus::bandwidth: { diff --git a/native/android/tests/activitymanager/UidImportanceHelperApps/Android.bp b/native/android/tests/activitymanager/UidImportanceHelperApps/Android.bp index 1a51218616d2..fdca3fad2383 100644 --- a/native/android/tests/activitymanager/UidImportanceHelperApps/Android.bp +++ b/native/android/tests/activitymanager/UidImportanceHelperApps/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test_helper_app { name: "UidImportanceHelperApp", manifest: "HelperAppManifest.xml", @@ -8,4 +17,3 @@ android_test_helper_app { "general-tests", ], } - diff --git a/native/android/tests/activitymanager/nativeTests/Android.bp b/native/android/tests/activitymanager/nativeTests/Android.bp index d4b5015ad8f3..528ac12c16b7 100644 --- a/native/android/tests/activitymanager/nativeTests/Android.bp +++ b/native/android/tests/activitymanager/nativeTests/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + cc_test { name: "ActivityManagerNativeTestCases", diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 91bad7b4aa91..f9795c601431 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -41,13 +41,13 @@ android:supportsRtl="true"> <service - android:name=".DeviceDiscoveryService" + android:name=".CompanionDeviceDiscoveryService" android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" android:exported="true"> </service> <activity - android:name=".DeviceChooserActivity" + android:name=".CompanionDeviceActivity" android:theme="@style/ChooserActivity" android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" android:exported="true"> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index c30d4bf322cf..f1a98adc0ab2 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static java.util.Objects.requireNonNull; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.companion.AssociationRequest; @@ -31,41 +32,52 @@ import android.companion.CompanionDeviceManager; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.TypedArray; import android.database.DataSetObserver; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Html; import android.util.Log; +import android.util.SparseArray; +import android.util.TypedValue; import android.view.Gravity; import android.view.View; -import android.widget.AdapterView; +import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.companiondevicemanager.DeviceDiscoveryService.DeviceFilterPair; +import com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DeviceFilterPair; import com.android.internal.util.Preconditions; -public class DeviceChooserActivity extends Activity { +public class CompanionDeviceActivity extends Activity { private static final boolean DEBUG = false; - private static final String LOG_TAG = "DeviceChooserActivity"; + private static final String LOG_TAG = CompanionDeviceActivity.class.getSimpleName(); + + static CompanionDeviceActivity sInstance; View mLoadingIndicator = null; ListView mDeviceListView; private View mPairButton; private View mCancelButton; + DevicesAdapter mDevicesAdapter; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (DEBUG) Log.i(LOG_TAG, "Started with intent " + getIntent()); + Log.i(LOG_TAG, "Starting UI for " + getService().mRequest); if (getService().mDevicesFound.isEmpty()) { Log.e(LOG_TAG, "About to show UI, but no devices to show"); } getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + sInstance = this; String deviceProfile = getRequest().getDeviceProfile(); String profilePrivacyDisclaimer = emptyIfNull(getRequest() @@ -96,17 +108,14 @@ public class DeviceChooserActivity extends Activity { profileName, getCallingAppName()), 0)); mDeviceListView = findViewById(R.id.device_list); - final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter; - mDeviceListView.setAdapter(adapter); - mDeviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) { - getService().mSelectedDevice = - (DeviceFilterPair) adapterView.getItemAtPosition(pos); - adapter.notifyDataSetChanged(); - } + mDevicesAdapter = new DevicesAdapter(); + mDeviceListView.setAdapter(mDevicesAdapter); + mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> { + getService().mSelectedDevice = + (DeviceFilterPair) adapterView.getItemAtPosition(pos); + mDevicesAdapter.notifyDataSetChanged(); }); - adapter.registerDataSetObserver(new DataSetObserver() { + mDevicesAdapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { onSelectionUpdate(); @@ -133,6 +142,12 @@ public class DeviceChooserActivity extends Activity { mCancelButton.setOnClickListener(v -> cancel()); } + static void notifyDevicesChanged() { + if (sInstance != null && sInstance.mDevicesAdapter != null && !sInstance.isFinishing()) { + sInstance.mDevicesAdapter.notifyDataSetChanged(); + } + } + private AssociationRequest getRequest() { return getService().mRequest; } @@ -156,6 +171,7 @@ public class DeviceChooserActivity extends Activity { } private void cancel() { + Log.i(LOG_TAG, "cancel()"); getService().onCancel(); setResult(RESULT_CANCELED); finish(); @@ -170,6 +186,14 @@ public class DeviceChooserActivity extends Activity { } } + @Override + protected void onDestroy() { + super.onDestroy(); + if (sInstance == this) { + sInstance = null; + } + } + private CharSequence getCallingAppName() { try { final PackageManager packageManager = getPackageManager(); @@ -217,15 +241,91 @@ public class DeviceChooserActivity extends Activity { } } - private DeviceDiscoveryService getService() { - return DeviceDiscoveryService.sInstance; + private CompanionDeviceDiscoveryService getService() { + return CompanionDeviceDiscoveryService.sInstance; } - protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) { + protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) { + Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")"); getService().onDeviceSelected( getCallingPackage(), getDeviceMacAddress(selectedDevice.device)); setResult(RESULT_OK, new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device)); finish(); } + + class DevicesAdapter extends BaseAdapter { + private final Drawable mBluetoothIcon = icon(android.R.drawable.stat_sys_data_bluetooth); + private final Drawable mWifiIcon = icon(com.android.internal.R.drawable.ic_wifi_signal_3); + + private SparseArray<Integer> mColors = new SparseArray(); + + private Drawable icon(int drawableRes) { + Drawable icon = getResources().getDrawable(drawableRes, null); + icon.setTint(Color.DKGRAY); + return icon; + } + + @Override + public View getView( + int position, + @Nullable View convertView, + @NonNull ViewGroup parent) { + TextView view = convertView instanceof TextView + ? (TextView) convertView + : newView(); + bind(view, getItem(position)); + return view; + } + + private void bind(TextView textView, DeviceFilterPair device) { + textView.setText(device.getDisplayName()); + textView.setBackgroundColor( + device.equals(getService().mSelectedDevice) + ? getColor(android.R.attr.colorControlHighlight) + : Color.TRANSPARENT); + textView.setCompoundDrawablesWithIntrinsicBounds( + device.device instanceof android.net.wifi.ScanResult + ? mWifiIcon + : mBluetoothIcon, + null, null, null); + textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground)); + } + + private TextView newView() { + final TextView textView = new TextView(CompanionDeviceActivity.this); + textView.setTextColor(getColor(android.R.attr.colorForeground)); + final int padding = CompanionDeviceActivity.getPadding(getResources()); + textView.setPadding(padding, padding, padding, padding); + textView.setCompoundDrawablePadding(padding); + return textView; + } + + private int getColor(int colorAttr) { + if (mColors.contains(colorAttr)) { + return mColors.get(colorAttr); + } + TypedValue typedValue = new TypedValue(); + TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr }); + int result = a.getColor(0, 0); + a.recycle(); + mColors.put(colorAttr, result); + return result; + } + + @Override + public int getCount() { + return getService().mDevicesFound.size(); + } + + @Override + public DeviceFilterPair getItem(int position) { + return getService().mDevicesFound.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + } } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index 67d4b41f164c..e620dfbafa08 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -50,9 +50,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.IBinder; @@ -60,12 +57,6 @@ import android.os.Parcelable; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; -import android.util.SparseArray; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.ArrayUtils; @@ -76,14 +67,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -public class DeviceDiscoveryService extends Service { +public class CompanionDeviceDiscoveryService extends Service { private static final boolean DEBUG = false; - private static final String LOG_TAG = "DeviceDiscoveryService"; + private static final String LOG_TAG = CompanionDeviceDiscoveryService.class.getSimpleName(); private static final long SCAN_TIMEOUT = 20000; - static DeviceDiscoveryService sInstance; + static CompanionDeviceDiscoveryService sInstance; private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; @@ -102,12 +93,12 @@ public class DeviceDiscoveryService extends Service { AssociationRequest mRequest; List<DeviceFilterPair> mDevicesFound; DeviceFilterPair mSelectedDevice; - DevicesAdapter mDevicesAdapter; IFindDeviceCallback mFindCallback; AndroidFuture<Association> mServiceCallback; boolean mIsScanning = false; - @Nullable DeviceChooserActivity mActivity = null; + @Nullable + CompanionDeviceActivity mActivity = null; private final ICompanionDeviceDiscoveryService mBinder = new ICompanionDeviceDiscoveryService.Stub() { @@ -125,7 +116,8 @@ public class DeviceDiscoveryService extends Service { mFindCallback = findCallback; mServiceCallback = serviceCallback; Handler.getMain().sendMessage(obtainMessage( - DeviceDiscoveryService::startDiscovery, DeviceDiscoveryService.this, request)); + CompanionDeviceDiscoveryService::startDiscovery, + CompanionDeviceDiscoveryService.this, request)); } }; @@ -151,7 +143,6 @@ public class DeviceDiscoveryService extends Service { mWifiManager = getSystemService(WifiManager.class); mDevicesFound = new ArrayList<>(); - mDevicesAdapter = new DevicesAdapter(); sInstance = this; } @@ -165,7 +156,8 @@ public class DeviceDiscoveryService extends Service { mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class); mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class); mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class); - mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter); + mBLEScanFilters + = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter); reset(); } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request); @@ -223,7 +215,7 @@ public class DeviceDiscoveryService extends Service { } mIsScanning = true; Handler.getMain().sendMessageDelayed( - obtainMessage(DeviceDiscoveryService::stopScan, this), + obtainMessage(CompanionDeviceDiscoveryService::stopScan, this), SCAN_TIMEOUT); } @@ -237,7 +229,7 @@ public class DeviceDiscoveryService extends Service { stopScan(); mDevicesFound.clear(); mSelectedDevice = null; - mDevicesAdapter.notifyDataSetChanged(); + CompanionDeviceActivity.notifyDevicesChanged(); } @Override @@ -252,7 +244,7 @@ public class DeviceDiscoveryService extends Service { if (!mIsScanning) return; mIsScanning = false; - DeviceChooserActivity activity = mActivity; + CompanionDeviceActivity activity = mActivity; if (activity != null) { if (activity.mDeviceListView != null) { activity.mDeviceListView.removeFooterView(activity.mLoadingIndicator); @@ -276,7 +268,7 @@ public class DeviceDiscoveryService extends Service { if (device == null) return; Handler.getMain().sendMessage(obtainMessage( - DeviceDiscoveryService::onDeviceFoundMainThread, this, device)); + CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device)); } @MainThread @@ -292,7 +284,7 @@ public class DeviceDiscoveryService extends Service { onReadyToShowUI(); } mDevicesFound.add(device); - mDevicesAdapter.notifyDataSetChanged(); + CompanionDeviceActivity.notifyDevicesChanged(); } //TODO also, on timeout -> call onFailure @@ -300,7 +292,7 @@ public class DeviceDiscoveryService extends Service { try { mFindCallback.onSuccess(PendingIntent.getActivity( this, 0, - new Intent(this, DeviceChooserActivity.class), + new Intent(this, CompanionDeviceActivity.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); } catch (RemoteException e) { @@ -311,13 +303,13 @@ public class DeviceDiscoveryService extends Service { private void onDeviceLost(@Nullable DeviceFilterPair device) { Log.i(LOG_TAG, "Lost device " + device.getDisplayName()); Handler.getMain().sendMessage(obtainMessage( - DeviceDiscoveryService::onDeviceLostMainThread, this, device)); + CompanionDeviceDiscoveryService::onDeviceLostMainThread, this, device)); } @MainThread void onDeviceLostMainThread(@Nullable DeviceFilterPair device) { mDevicesFound.remove(device); - mDevicesAdapter.notifyDataSetChanged(); + CompanionDeviceActivity.notifyDevicesChanged(); } void onDeviceSelected(String callingPackage, String deviceAddress) { @@ -331,81 +323,6 @@ public class DeviceDiscoveryService extends Service { mServiceCallback.cancel(true); } - class DevicesAdapter extends BaseAdapter { - private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth); - private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3); - - private SparseArray<Integer> mColors = new SparseArray(); - - private Drawable icon(int drawableRes) { - Drawable icon = getResources().getDrawable(drawableRes, null); - icon.setTint(Color.DKGRAY); - return icon; - } - - @Override - public View getView( - int position, - @Nullable View convertView, - @NonNull ViewGroup parent) { - TextView view = convertView instanceof TextView - ? (TextView) convertView - : newView(); - bind(view, getItem(position)); - return view; - } - - private void bind(TextView textView, DeviceFilterPair device) { - textView.setText(device.getDisplayName()); - textView.setBackgroundColor( - device.equals(mSelectedDevice) - ? getColor(android.R.attr.colorControlHighlight) - : Color.TRANSPARENT); - textView.setCompoundDrawablesWithIntrinsicBounds( - device.device instanceof android.net.wifi.ScanResult - ? WIFI_ICON - : BLUETOOTH_ICON, - null, null, null); - textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground)); - } - - private TextView newView() { - final TextView textView = new TextView(DeviceDiscoveryService.this); - textView.setTextColor(getColor(android.R.attr.colorForeground)); - final int padding = DeviceChooserActivity.getPadding(getResources()); - textView.setPadding(padding, padding, padding, padding); - textView.setCompoundDrawablePadding(padding); - return textView; - } - - private int getColor(int colorAttr) { - if (mColors.contains(colorAttr)) { - return mColors.get(colorAttr); - } - TypedValue typedValue = new TypedValue(); - TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr }); - int result = a.getColor(0, 0); - a.recycle(); - mColors.put(colorAttr, result); - return result; - } - - @Override - public int getCount() { - return mDevicesFound.size(); - } - - @Override - public DeviceFilterPair getItem(int position) { - return mDevicesFound.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - } - /** * A pair of device and a filter that matched this device if any. * diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index c7e261c93689..86b85e8398ce 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -58,3 +58,28 @@ filegroup { "//packages/modules/Connectivity:__subpackages__", ], } + +java_sdk_library { + name: "framework-connectivity", + api_only: true, + defaults: ["framework-module-defaults"], + // TODO: build against module API + platform_apis: true, + srcs: [ + ":framework-connectivity-sources", + ], + aidl: { + include_dirs: [ + // Include directories for parcelables that are part of the stable API, and need a + // one-line "parcelable X" .aidl declaration to be used in AIDL interfaces. + // TODO(b/180293679): remove these dependencies as they should not be necessary once + // the module builds against API (the parcelable declarations exist in framework.aidl) + "frameworks/base/core/java", // For framework parcelables + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, + libs: [ + "unsupportedappusage", + ], + permitted_packages: ["android.net", "com.android.connectivity.aidl"], +} diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt new file mode 100644 index 000000000000..31b8fc8ae53a --- /dev/null +++ b/packages/Connectivity/framework/api/current.txt @@ -0,0 +1,470 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public int describeContents(); + method public void ignoreNetwork(); + method public void reportCaptivePortalDismissed(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR; + } + + public class ConnectivityDiagnosticsManager { + method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + } + + public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { + ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); + method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport); + method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport); + method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); + } + + public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdditionalInfo(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; + field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; + field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; + field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + field public static final int NETWORK_PROBE_DNS = 4; // 0x4 + field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 + field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 + field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 + field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 + field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 + field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 + field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 + field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 + } + + public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method public int getDetectionMethod(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method @NonNull public android.os.PersistableBundle getStallDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 + field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; + field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + } + + public class ConnectivityManager { + method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); + method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); + method @Deprecated public boolean getBackgroundDataSetting(); + method @Nullable public android.net.Network getBoundNetworkForProcess(); + method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); + method @Nullable public android.net.ProxyInfo getDefaultProxy(); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); + method @Nullable public byte[] getNetworkWatchlistConfigHash(); + method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); + method public int getRestrictBackgroundStatus(); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); + method public boolean isDefaultNetworkActive(); + method @Deprecated public static boolean isNetworkTypeValid(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); + method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); + method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); + method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); + method public boolean requestBandwidthUpdate(@NonNull android.net.Network); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method @Deprecated public void setNetworkPreference(int); + method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); + method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); + field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 + field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; + field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; + field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; + field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; + field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + field public static final String EXTRA_REASON = "reason"; + field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 + field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 + field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 + field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 + field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 + field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 + field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 + field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 + field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 + field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 + field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 + field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 + field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 + field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 + field @Deprecated public static final int TYPE_VPN = 17; // 0x11 + field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 + field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 + } + + public static class ConnectivityManager.NetworkCallback { + ctor public ConnectivityManager.NetworkCallback(); + method public void onAvailable(@NonNull android.net.Network); + method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); + method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); + method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); + method public void onLosing(@NonNull android.net.Network, int); + method public void onLost(@NonNull android.net.Network); + method public void onUnavailable(); + } + + public static interface ConnectivityManager.OnNetworkActiveListener { + method public void onNetworkActive(); + } + + public class DhcpInfo implements android.os.Parcelable { + ctor public DhcpInfo(); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR; + field public int dns1; + field public int dns2; + field public int gateway; + field public int ipAddress; + field public int leaseDuration; + field public int netmask; + field public int serverAddress; + } + + public final class DnsResolver { + method @NonNull public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); + method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); + method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); + method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); + field public static final int CLASS_IN = 1; // 0x1 + field public static final int ERROR_PARSE = 0; // 0x0 + field public static final int ERROR_SYSTEM = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.Callback<T> { + method public void onAnswer(@NonNull T, int); + method public void onError(@NonNull android.net.DnsResolver.DnsException); + } + + public static class DnsResolver.DnsException extends java.lang.Exception { + field public final int code; + } + + public class InetAddresses { + method public static boolean isNumericAddress(@NonNull String); + method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); + } + + public final class IpPrefix implements android.os.Parcelable { + method public boolean contains(@NonNull java.net.InetAddress); + method public int describeContents(); + method @NonNull public java.net.InetAddress getAddress(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method @NonNull public byte[] getRawAddress(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; + } + + public class LinkAddress implements android.os.Parcelable { + method public int describeContents(); + method public java.net.InetAddress getAddress(); + method public int getFlags(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method public int getScope(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR; + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(); + method public boolean addRoute(@NonNull android.net.RouteInfo); + method public void clear(); + method public int describeContents(); + method @Nullable public java.net.Inet4Address getDhcpServerAddress(); + method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @Nullable public String getInterfaceName(); + method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses(); + method public int getMtu(); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method @Nullable public String getPrivateDnsServerName(); + method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(); + method public boolean isPrivateDnsActive(); + method public boolean isWakeOnLanSupported(); + method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); + method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); + method public void setDomains(@Nullable String); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setInterfaceName(@Nullable String); + method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>); + method public void setMtu(int); + method public void setNat64Prefix(@Nullable android.net.IpPrefix); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR; + } + + public final class MacAddress implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); + method @NonNull public static android.net.MacAddress fromString(@NonNull String); + method public int getAddressType(); + method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); + method public boolean isLocallyAssigned(); + method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); + method @NonNull public byte[] toByteArray(); + method @NonNull public String toOuiString(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.net.MacAddress BROADCAST_ADDRESS; + field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR; + field public static final int TYPE_BROADCAST = 3; // 0x3 + field public static final int TYPE_MULTICAST = 2; // 0x2 + field public static final int TYPE_UNICAST = 1; // 0x1 + } + + public class Network implements android.os.Parcelable { + method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; + method public void bindSocket(java.net.Socket) throws java.io.IOException; + method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; + method public int describeContents(); + method public static android.net.Network fromNetworkHandle(long); + method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; + method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; + method public long getNetworkHandle(); + method public javax.net.SocketFactory getSocketFactory(); + method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; + method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR; + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(); + ctor public NetworkCapabilities(android.net.NetworkCapabilities); + method public int describeContents(); + method public int getLinkDownstreamBandwidthKbps(); + method public int getLinkUpstreamBandwidthKbps(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public int getOwnerUid(); + method public int getSignalStrength(); + method @Nullable public android.net.TransportInfo getTransportInfo(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; + field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 + field public static final int NET_CAPABILITY_CBS = 5; // 0x5 + field public static final int NET_CAPABILITY_DUN = 2; // 0x2 + field public static final int NET_CAPABILITY_EIMS = 10; // 0xa + field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d + field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 + field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 + field public static final int NET_CAPABILITY_IA = 7; // 0x7 + field public static final int NET_CAPABILITY_IMS = 4; // 0x4 + field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc + field public static final int NET_CAPABILITY_MCX = 23; // 0x17 + field public static final int NET_CAPABILITY_MMS = 0; // 0x0 + field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 + field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb + field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 + field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf + field public static final int NET_CAPABILITY_RCS = 8; // 0x8 + field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 + field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 + field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe + field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 + field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 + field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 + field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 + field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 + field public static final int TRANSPORT_CELLULAR = 0; // 0x0 + field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_LOWPAN = 6; // 0x6 + field public static final int TRANSPORT_VPN = 4; // 0x4 + field public static final int TRANSPORT_WIFI = 1; // 0x1 + field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 + } + + @Deprecated public class NetworkInfo implements android.os.Parcelable { + ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); + method @Deprecated public int describeContents(); + method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); + method @Deprecated public String getExtraInfo(); + method @Deprecated public String getReason(); + method @Deprecated public android.net.NetworkInfo.State getState(); + method @Deprecated public int getSubtype(); + method @Deprecated public String getSubtypeName(); + method @Deprecated public int getType(); + method @Deprecated public String getTypeName(); + method @Deprecated public boolean isAvailable(); + method @Deprecated public boolean isConnected(); + method @Deprecated public boolean isConnectedOrConnecting(); + method @Deprecated public boolean isFailover(); + method @Deprecated public boolean isRoaming(); + method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; + } + + @Deprecated public enum NetworkInfo.DetailedState { + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; + } + + @Deprecated public enum NetworkInfo.State { + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; + } + + public class NetworkRequest implements android.os.Parcelable { + method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); + method public int describeContents(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR; + } + + public static class NetworkRequest.Builder { + ctor public NetworkRequest.Builder(); + method public android.net.NetworkRequest.Builder addCapability(int); + method public android.net.NetworkRequest.Builder addTransportType(int); + method public android.net.NetworkRequest build(); + method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); + method public android.net.NetworkRequest.Builder removeCapability(int); + method public android.net.NetworkRequest.Builder removeTransportType(int); + method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); + method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); + } + + public final class Proxy { + ctor public Proxy(); + method @Deprecated public static String getDefaultHost(); + method @Deprecated public static int getDefaultPort(); + method @Deprecated public static String getHost(android.content.Context); + method @Deprecated public static int getPort(android.content.Context); + field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + } + + public class ProxyInfo implements android.os.Parcelable { + ctor public ProxyInfo(@Nullable android.net.ProxyInfo); + method public static android.net.ProxyInfo buildDirectProxy(String, int); + method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>); + method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); + method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); + method public int describeContents(); + method public String[] getExclusionList(); + method public String getHost(); + method public android.net.Uri getPacFileUrl(); + method public int getPort(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR; + } + + public final class RouteInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.IpPrefix getDestination(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public String getInterface(); + method public boolean hasGateway(); + method public boolean isDefaultRoute(); + method public boolean matches(java.net.InetAddress); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR; + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + + public interface TransportInfo { + } + +} + diff --git a/packages/Connectivity/framework/api/lint-baseline.txt b/packages/Connectivity/framework/api/lint-baseline.txt new file mode 100644 index 000000000000..2f4004ab723d --- /dev/null +++ b/packages/Connectivity/framework/api/lint-baseline.txt @@ -0,0 +1,4 @@ +// Baseline format: 1.0 +VisiblySynchronized: android.net.NetworkInfo#toString(): + Internal locks must not be exposed (synchronizing on this or class is still + externally observable): method android.net.NetworkInfo.toString() diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt new file mode 100644 index 000000000000..3af855ec1e11 --- /dev/null +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -0,0 +1,66 @@ +// Signature format: 2.0 +package android.net { + + public final class ConnectivityFrameworkInitializer { + method public static void registerServiceWrappers(); + } + + public class ConnectivityManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method @Nullable public String getSubscriberId(); + } + + public static final class NetworkAgentConfig.Builder { + method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + field public static final int TRANSPORT_TEST = 7; // 0x7 + } + + public final class Proxy { + method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); + } + + public final class TcpRepairWindow { + ctor public TcpRepairWindow(int, int, int, int, int, int); + field public final int maxWindow; + field public final int rcvWnd; + field public final int rcvWndScale; + field public final int rcvWup; + field public final int sndWl1; + field public final int sndWnd; + } + + public final class TestNetworkInterface implements android.os.Parcelable { + ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); + method public int describeContents(); + method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); + method @NonNull public String getInterfaceName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR; + } + + public class TestNetworkManager { + method @NonNull public android.net.TestNetworkInterface createTapInterface(); + method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>); + method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); + method public void teardownTestNetwork(@NonNull android.net.Network); + field public static final String TEST_TAP_PREFIX = "testtap"; + } + + public final class 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; + } + +} + diff --git a/packages/Connectivity/framework/api/module-lib-removed.txt b/packages/Connectivity/framework/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Connectivity/framework/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Connectivity/framework/api/removed.txt b/packages/Connectivity/framework/api/removed.txt new file mode 100644 index 000000000000..303a1e6173ba --- /dev/null +++ b/packages/Connectivity/framework/api/removed.txt @@ -0,0 +1,11 @@ +// Signature format: 2.0 +package android.net { + + public class ConnectivityManager { + method @Deprecated public boolean requestRouteToHost(int, int); + method @Deprecated public int startUsingNetworkFeature(int, String); + method @Deprecated public int stopUsingNetworkFeature(int, String); + } + +} + diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt new file mode 100644 index 000000000000..41ebc5774f3d --- /dev/null +++ b/packages/Connectivity/framework/api/system-current.txt @@ -0,0 +1,407 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public void logEvent(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); + method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 + field public static final int APP_RETURN_DISMISSED = 0; // 0x0 + field public static final int APP_RETURN_UNWANTED = 1; // 0x1 + field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 + } + + public final class CaptivePortalData implements android.os.Parcelable { + method public int describeContents(); + method public long getByteLimit(); + method public long getExpiryTimeMillis(); + method public long getRefreshTimeMillis(); + method @Nullable public android.net.Uri getUserPortalUrl(); + method public int getUserPortalUrlSource(); + method @Nullable public String getVenueFriendlyName(); + method @Nullable public android.net.Uri getVenueInfoUrl(); + method public int getVenueInfoUrlSource(); + method public boolean isCaptive(); + method public boolean isSessionExtendable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; + } + + public static class CaptivePortalData.Builder { + ctor public CaptivePortalData.Builder(); + ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); + method @NonNull public android.net.CaptivePortalData build(); + method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); + method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); + method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); + } + + public class ConnectivityManager { + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); + method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); + method public void unregisterQosCallback(@NonNull android.net.QosCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TYPE_NONE = -1; // 0xffffffff + field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 + field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd + } + + public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { + method public void onComplete(); + } + + @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { + ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); + method @Deprecated public void onTetheringFailed(); + method @Deprecated public void onTetheringStarted(); + } + + @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { + method @Deprecated public void onTetheringEntitlementResult(int); + } + + @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { + ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); + method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); + } + + public final class InvalidPacketException extends java.lang.Exception { + ctor public InvalidPacketException(int); + method public int getError(); + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + } + + public final class IpConfiguration implements android.os.Parcelable { + ctor public IpConfiguration(); + ctor public IpConfiguration(@NonNull android.net.IpConfiguration); + method public int describeContents(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); + method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); + method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); + method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); + method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR; + } + + public enum IpConfiguration.IpAssignment { + enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; + enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; + enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; + } + + public enum IpConfiguration.ProxySettings { + enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; + enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; + enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; + enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; + } + + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public IpPrefix(@NonNull String); + } + + public class KeepalivePacketData { + ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; + method @NonNull public java.net.InetAddress getDstAddress(); + method public int getDstPort(); + method @NonNull public byte[] getPacket(); + method @NonNull public java.net.InetAddress getSrcAddress(); + method public int getSrcPort(); + } + + public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public LinkAddress(@NonNull String); + ctor public LinkAddress(@NonNull String, int, int); + method public long getDeprecationTime(); + method public long getExpirationTime(); + method public boolean isGlobalPreferred(); + method public boolean isIpv4(); + method public boolean isIpv6(); + method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); + field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL + field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(@Nullable android.net.LinkProperties); + ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); + method public boolean addDnsServer(@NonNull java.net.InetAddress); + method public boolean addLinkAddress(@NonNull android.net.LinkAddress); + method public boolean addPcscfServer(@NonNull java.net.InetAddress); + method @NonNull public java.util.List<java.net.InetAddress> getAddresses(); + method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames(); + method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses(); + method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes(); + method @Nullable public android.net.Uri getCaptivePortalApiUrl(); + method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); + method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); + method @Nullable public String getTcpBufferSizes(); + method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); + method public boolean hasGlobalIpv6Address(); + method public boolean hasIpv4Address(); + method public boolean hasIpv4DefaultRoute(); + method public boolean hasIpv4DnsServer(); + method public boolean hasIpv6DefaultRoute(); + method public boolean hasIpv6DnsServer(); + method public boolean isIpv4Provisioned(); + method public boolean isIpv6Provisioned(); + method public boolean isProvisioned(); + method public boolean isReachable(@NonNull java.net.InetAddress); + method public boolean removeDnsServer(@NonNull java.net.InetAddress); + method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); + method public boolean removeRoute(@NonNull android.net.RouteInfo); + method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); + method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); + method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); + method public void setPrivateDnsServerName(@Nullable String); + method public void setTcpBufferSizes(@Nullable String); + method public void setUsePrivateDns(boolean); + method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); + } + + public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; + } + + public class Network implements android.os.Parcelable { + ctor public Network(@NonNull android.net.Network); + method public int getNetId(); + method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); + } + + public abstract class NetworkAgent { + ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); + method @Nullable public android.net.Network getNetwork(); + method public void markConnected(); + method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); + method public void onAutomaticReconnectDisabled(); + method public void onNetworkUnwanted(); + method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); + method public void onQosCallbackUnregistered(int); + method public void onRemoveKeepalivePacketFilter(int); + method public void onSaveAcceptUnvalidated(boolean); + method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); + method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); + method public void onStopSocketKeepalive(int); + method public void onValidationStatus(int, @Nullable android.net.Uri); + method @NonNull public android.net.Network register(); + method public final void sendLinkProperties(@NonNull android.net.LinkProperties); + method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method public final void sendNetworkScore(@IntRange(from=0, to=99) int); + method public final void sendQosCallbackError(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); + method public final void sendQosSessionLost(int, int); + method public final void sendSocketKeepaliveEvent(int, int); + method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); + method public void unregister(); + field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 + field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getLegacyType(); + method @NonNull public String getLegacyTypeName(); + method public boolean isExplicitlySelected(); + method public boolean isPartialConnectivityAcceptable(); + method public boolean isUnvalidatedConnectivityAcceptable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; + } + + public static final class NetworkAgentConfig.Builder { + ctor public NetworkAgentConfig.Builder(); + method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); + 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 + field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a + field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 + field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b + } + + public static final class NetworkCapabilities.Builder { + ctor public NetworkCapabilities.Builder(); + ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); + 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[]); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); + method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); + } + + public class NetworkProvider { + ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); + method public int getProviderId(); + method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); + method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); + field public static final int ID_NONE = -1; // 0xffffffff + } + + public class NetworkRequest implements android.os.Parcelable { + method @Nullable public String getRequestorPackageName(); + method public int getRequestorUid(); + } + + public static class NetworkRequest.Builder { + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); + } + + public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); + method public int getMtu(); + method public int getType(); + field public static final int RTN_THROW = 9; // 0x9 + field public static final int RTN_UNICAST = 1; // 0x1 + field public static final int RTN_UNREACHABLE = 7; // 0x7 + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + field public static final int SUCCESS = 0; // 0x0 + } + + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void addDnsServer(@NonNull java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public android.net.LinkAddress getIpAddress(); + method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + + public static final class StaticIpConfiguration.Builder { + ctor public StaticIpConfiguration.Builder(); + method @NonNull public android.net.StaticIpConfiguration build(); + method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>); + method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); + method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); + method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); + } + + public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR; + field public final int ipTos; + field public final int ipTtl; + field public final int tcpAck; + field public final int tcpSeq; + field public final int tcpWindow; + field public final int tcpWindowScale; + } + + public interface TransportInfo { + method public default boolean hasLocationSensitiveFields(); + method @NonNull public default android.net.TransportInfo makeCopy(boolean); + } + +} + +package android.net.apf { + + public final class ApfCapabilities implements android.os.Parcelable { + ctor public ApfCapabilities(int, int, int); + method public int describeContents(); + method public static boolean getApfDrop8023Frames(); + method @NonNull public static int[] getApfEtherTypeBlackList(); + method public boolean hasDataAccess(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.util { + + public final class SocketUtils { + method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; + method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; + method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); + method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); + } + +} + diff --git a/packages/Connectivity/framework/api/system-lint-baseline.txt b/packages/Connectivity/framework/api/system-lint-baseline.txt new file mode 100644 index 000000000000..9a97707763f1 --- /dev/null +++ b/packages/Connectivity/framework/api/system-lint-baseline.txt @@ -0,0 +1 @@ +// Baseline format: 1.0 diff --git a/packages/Connectivity/framework/api/system-removed.txt b/packages/Connectivity/framework/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Connectivity/framework/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java index f4b46e9f11ed..eafda4d2d694 100644 --- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java +++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java @@ -44,7 +44,7 @@ public final class CaptivePortalData implements Parcelable { private final boolean mCaptive; private final String mVenueFriendlyName; private final int mVenueInfoUrlSource; - private final int mTermsAndConditionsSource; + private final int mUserPortalUrlSource; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -65,7 +65,7 @@ public final class CaptivePortalData implements Parcelable { private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, - String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) { + String venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -75,7 +75,7 @@ public final class CaptivePortalData implements Parcelable { mCaptive = captive; mVenueFriendlyName = venueFriendlyName; mVenueInfoUrlSource = venueInfoUrlSource; - mTermsAndConditionsSource = termsAndConditionsSource; + mUserPortalUrlSource = userPortalUrlSource; } private CaptivePortalData(Parcel p) { @@ -100,7 +100,7 @@ public final class CaptivePortalData implements Parcelable { dest.writeBoolean(mCaptive); dest.writeString(mVenueFriendlyName); dest.writeInt(mVenueInfoUrlSource); - dest.writeInt(mTermsAndConditionsSource); + dest.writeInt(mUserPortalUrlSource); } /** @@ -130,7 +130,7 @@ public final class CaptivePortalData implements Parcelable { public Builder(@Nullable CaptivePortalData data) { if (data == null) return; setRefreshTime(data.mRefreshTimeMillis) - .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource) + .setUserPortalUrl(data.mUserPortalUrl, data.mUserPortalUrlSource) .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource) .setSessionExtendable(data.mIsSessionExtendable) .setBytesRemaining(data.mByteLimit) @@ -314,7 +314,7 @@ public final class CaptivePortalData implements Parcelable { * @return The source that the user portal URL was obtained from */ public @CaptivePortalDataSource int getUserPortalUrlSource() { - return mTermsAndConditionsSource; + return mUserPortalUrlSource; } /** @@ -342,7 +342,7 @@ public final class CaptivePortalData implements Parcelable { public int hashCode() { return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName, - mVenueInfoUrlSource, mTermsAndConditionsSource); + mVenueInfoUrlSource, mUserPortalUrlSource); } @Override @@ -358,7 +358,7 @@ public final class CaptivePortalData implements Parcelable { && mCaptive == other.mCaptive && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName) && mVenueInfoUrlSource == other.mVenueInfoUrlSource - && mTermsAndConditionsSource == other.mTermsAndConditionsSource; + && mUserPortalUrlSource == other.mUserPortalUrlSource; } @Override @@ -373,7 +373,7 @@ public final class CaptivePortalData implements Parcelable { + ", captive: " + mCaptive + ", venueFriendlyName: " + mVenueFriendlyName + ", venueInfoUrlSource: " + mVenueInfoUrlSource - + ", termsAndConditionsSource: " + mTermsAndConditionsSource + + ", userPortalUrlSource: " + mUserPortalUrlSource + "}"; } } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 4ddae533bb80..e7ab0a1c7ac8 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -1067,58 +1067,6 @@ public class ConnectivityManager { } /** - * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); - } - - /** - * Calls VpnManager#setAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) { - return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, - lockdownAllowlist); - } - - /** - * Calls VpnManager#getAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public String getAlwaysOnVpnPackageForUser(int userId) { - return getVpnManager().getAlwaysOnVpnPackageForUser(userId); - } - - /** - * Calls VpnManager#isVpnLockdownEnabled. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isVpnLockdownEnabled(int userId) { - return getVpnManager().isVpnLockdownEnabled(userId); - } - - /** - * Calls VpnManager#getVpnLockdownAllowlist. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public List<String> getVpnLockdownAllowlist(int userId) { - return getVpnManager().getVpnLockdownAllowlist(userId); - } - - /** * Adds or removes a requirement for given UID ranges to use the VPN. * * If set to {@code true}, informs the system that the UIDs in the specified ranges must not @@ -3153,16 +3101,6 @@ public class ConnectivityManager { } /** - * Calls VpnManager#updateLockdownVpn. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean updateLockdownVpn() { - return getVpnManager().updateLockdownVpn(); - } - - /** * Set sign in error notification to visible or invisible * * @hide @@ -4523,8 +4461,6 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); - // TODO: Migrate callers to VpnManager#factoryReset. - getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4818,15 +4754,6 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** - * 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 */ public ConnectivityDiagnosticsManager createDiagnosticsManager() { return new ConnectivityDiagnosticsManager(mContext, mService); diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.java b/packages/Connectivity/framework/src/android/net/IpPrefix.java index bcb65fab8571..d2ee7d13b05f 100644 --- a/packages/Connectivity/framework/src/android/net/IpPrefix.java +++ b/packages/Connectivity/framework/src/android/net/IpPrefix.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; +import com.android.net.module.util.NetUtils; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -59,7 +61,7 @@ public final class IpPrefix implements Parcelable { throw new IllegalArgumentException( "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); } - NetworkUtils.maskRawAddress(address, prefixLength); + NetUtils.maskRawAddress(address, prefixLength); } /** @@ -190,7 +192,7 @@ public final class IpPrefix implements Parcelable { if (addrBytes == null || addrBytes.length != this.address.length) { return false; } - NetworkUtils.maskRawAddress(addrBytes, prefixLength); + NetUtils.maskRawAddress(addrBytes, prefixLength); return Arrays.equals(this.address, addrBytes); } @@ -204,7 +206,7 @@ public final class IpPrefix implements Parcelable { public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { if (otherPrefix.getPrefixLength() < prefixLength) return false; final byte[] otherAddress = otherPrefix.getRawAddress(); - NetworkUtils.maskRawAddress(otherAddress, prefixLength); + NetUtils.maskRawAddress(otherAddress, prefixLength); return Arrays.equals(otherAddress, address); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 26d14cbfaa95..cd76f409b093 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -205,6 +205,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_OEM_PRIVATE, NET_CAPABILITY_VEHICLE_INTERNAL, NET_CAPABILITY_NOT_VCN_MANAGED, + NET_CAPABILITY_ENTERPRISE, }) public @interface NetCapability { } @@ -415,8 +416,17 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + /** + * Indicates that this network is intended for enterprise use. + * <p> + * 5G URSP rules may indicate that all data should use a connection dedicated for enterprise + * use. If the enterprise capability is requested, all enterprise traffic will be routed over + * the connection with this capability. + */ + public static final int NET_CAPABILITY_ENTERPRISE = 29; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_ENTERPRISE; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -474,7 +484,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_MCX) | (1 << NET_CAPABILITY_RCS) | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) - | (1 << NET_CAPABILITY_XCAP); + | (1 << NET_CAPABILITY_XCAP) + | (1 << NET_CAPABILITY_ENTERPRISE); /** * Capabilities that force network to be restricted. @@ -2028,8 +2039,9 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; - case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_VEHICLE_INTERNAL: return "VEHICLE_INTERNAL"; case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; + case NET_CAPABILITY_ENTERPRISE: return "ENTERPRISE"; default: return Integer.toString(capability); } } diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 4e3085f4704d..b4a651c0607e 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -16,6 +16,22 @@ 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; @@ -30,6 +46,8 @@ 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; @@ -154,8 +172,30 @@ 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. */ @@ -177,6 +217,7 @@ 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); } @@ -193,6 +234,9 @@ 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; } @@ -204,6 +248,9 @@ 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; } @@ -261,6 +308,9 @@ 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; } @@ -380,6 +430,25 @@ 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/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java index 8be4af7b1396..9ccb04a44af4 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java +++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java @@ -29,7 +29,6 @@ import java.math.BigInteger; import java.net.Inet4Address; import java.net.InetAddress; import java.net.SocketException; -import java.net.UnknownHostException; import java.util.Locale; import java.util.TreeSet; @@ -232,46 +231,6 @@ public class NetworkUtils { } /** - * Masks a raw IP address byte array with the specified prefix length. - */ - public static void maskRawAddress(byte[] array, int prefixLength) { - if (prefixLength < 0 || prefixLength > array.length * 8) { - throw new RuntimeException("IP address with " + array.length + - " bytes has invalid prefix length " + prefixLength); - } - - int offset = prefixLength / 8; - int remainder = prefixLength % 8; - byte mask = (byte)(0xFF << (8 - remainder)); - - if (offset < array.length) array[offset] = (byte)(array[offset] & mask); - - offset++; - - for (; offset < array.length; offset++) { - array[offset] = 0; - } - } - - /** - * Get InetAddress masked with prefixLength. Will never return null. - * @param address the IP address to mask with - * @param prefixLength the prefixLength used to mask the IP - */ - public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { - byte[] array = address.getAddress(); - maskRawAddress(array, prefixLength); - - InetAddress netPart = null; - try { - netPart = InetAddress.getByAddress(array); - } catch (UnknownHostException e) { - throw new RuntimeException("getNetworkPart error - " + e.toString()); - } - return netPart; - } - - /** * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java index 85e3fa3048ed..43fffd733e91 100644 --- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -40,6 +40,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; /** * A class to encapsulate management of the "Smart Networking" capability of @@ -73,6 +75,32 @@ public class MultinetworkPolicyTracker { private volatile int mMeteredMultipathPreference; private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + // Mainline module can't use internal HandlerExecutor, so add an identical executor here. + private static class HandlerExecutor implements Executor { + @NonNull + private final Handler mHandler; + + HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } + } + + @VisibleForTesting + protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener + implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveSubId = subId; + reevaluateInternal(); + } + } + public MultinetworkPolicyTracker(Context ctx, Handler handler) { this(ctx, handler, null); } @@ -93,14 +121,8 @@ public class MultinetworkPolicyTracker { } }; - ctx.getSystemService(TelephonyManager.class).listen( - new PhoneStateListener(handler.getLooper()) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveSubId = subId; - reevaluateInternal(); - } - }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener( + new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener()); updateAvoidBadWifi(); updateMeteredMultipathPreference(); diff --git a/packages/InputDevices/OWNERS b/packages/InputDevices/OWNERS index 0313a40f7270..f0d6db88bcc5 100644 --- a/packages/InputDevices/OWNERS +++ b/packages/InputDevices/OWNERS @@ -1,2 +1 @@ -michaelwr@google.com -svv@google.com +include /services/core/java/com/android/server/input/OWNERS diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java index 8b4db92910f5..2946db3cdcf0 100644 --- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java @@ -27,9 +27,11 @@ public class LocalTransportParameters extends KeyValueSettingObserver { private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS; private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag"; private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only"; + private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer"; private boolean mFakeEncryptionFlag; private boolean mIsNonIncrementalOnly; + private boolean mIsDeviceTransfer; public LocalTransportParameters(Handler handler, ContentResolver resolver) { super(handler, resolver, Settings.Secure.getUriFor(SETTING)); @@ -43,6 +45,10 @@ public class LocalTransportParameters extends KeyValueSettingObserver { return mIsNonIncrementalOnly; } + boolean isDeviceTransfer() { + return mIsDeviceTransfer; + } + public String getSettingValue(ContentResolver resolver) { return Settings.Secure.getString(resolver, SETTING); } @@ -50,5 +56,6 @@ public class LocalTransportParameters extends KeyValueSettingObserver { public void update(KeyValueListParser parser) { mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false); + mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false); } } diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp index 095975afa13a..82e837bcd3ac 100644 --- a/packages/SettingsLib/BannerMessagePreference/Android.bp +++ b/packages/SettingsLib/BannerMessagePreference/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibBannerMessagePreference", diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index c23ff05f34ab..ed49bf4d5385 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibCollapsingToolbarBaseActivity", diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp index 3c41f7834d6c..25b4905c438f 100644 --- a/packages/SettingsLib/EmergencyNumber/Android.bp +++ b/packages/SettingsLib/EmergencyNumber/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibEmergencyNumber", diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp index 1af967fef717..11f39e7bb210 100644 --- a/packages/SettingsLib/FooterPreference/Android.bp +++ b/packages/SettingsLib/FooterPreference/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibFooterPreference", diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp index 1dc18f5cc4d2..1feec21e24e4 100644 --- a/packages/SettingsLib/MainSwitchPreference/Android.bp +++ b/packages/SettingsLib/MainSwitchPreference/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibMainSwitchPreference", diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml index 52779bcabf00..85c01c5732ca 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" + android:background="?android:attr/colorBackground" android:orientation="vertical"> <LinearLayout diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 74b65780ffc2..1c9298ed6085 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -17,6 +17,7 @@ package com.android.settingslib.widget; import android.content.Context; +import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -29,6 +30,7 @@ import android.widget.Switch; import android.widget.TextView; import androidx.annotation.VisibleForTesting; +import androidx.core.content.res.TypedArrayUtils; import com.android.settingslib.RestrictedLockUtils; @@ -88,6 +90,17 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec }); setChecked(mSwitch.isChecked()); + + if (attrs != null) { + final TypedArray a = context.obtainStyledAttributes(attrs, + androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, + 0 /*defStyleRes*/); + final CharSequence title = TypedArrayUtils.getText(a, + androidx.preference.R.styleable.Preference_title, + androidx.preference.R.styleable.Preference_android_title); + setTitle(title); + a.recycle(); + } } @Override @@ -126,7 +139,7 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec /** * Set the title text */ - public void setTitle(String text) { + public void setTitle(CharSequence text) { if (mTextView != null) { mTextView.setText(text); } diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java index 274bf8df2222..35afec38dd3d 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java @@ -18,7 +18,6 @@ package com.android.settingslib.widget; import android.content.Context; import android.content.res.TypedArray; -import android.text.TextUtils; import android.util.AttributeSet; import androidx.core.content.res.TypedArrayUtils; @@ -40,7 +39,7 @@ public class MainSwitchPreference extends TwoStatePreference { private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>(); private MainSwitchBar mMainSwitchBar; - private String mTitle; + private CharSequence mTitle; private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin; @@ -81,24 +80,28 @@ public class MainSwitchPreference extends TwoStatePreference { setLayoutResource(R.layout.main_switch_layout); if (attrs != null) { - TypedArray a = context.obtainStyledAttributes(attrs, + final TypedArray a = context.obtainStyledAttributes(attrs, androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); final CharSequence title = TypedArrayUtils.getText(a, androidx.preference.R.styleable.Preference_title, androidx.preference.R.styleable.Preference_android_title); - if (!TextUtils.isEmpty(title)) { - setTitle(title.toString()); - } + setTitle(title); a.recycle(); } } - /** - * Set the preference title text - */ - public void setTitle(String text) { - mTitle = text; + @Override + public void setChecked(boolean checked) { + super.setChecked(checked); + if (mMainSwitchBar != null) { + mMainSwitchBar.setChecked(checked); + } + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; if (mMainSwitchBar != null) { mMainSwitchBar.setTitle(mTitle); } diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp index 03becbd23226..957728120c4d 100644 --- a/packages/SettingsLib/TopIntroPreference/Android.bp +++ b/packages/SettingsLib/TopIntroPreference/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibTopIntroPreference", diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp index 3331550d0535..ad6e7ab9f564 100644 --- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp +++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_library { name: "SettingsLibUsageProgressBarPreference", diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 32419f49d8c9..1f3e0e9fe038 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -205,7 +205,6 @@ public class LocalMediaManager implements BluetoothCallback { void dispatchDeviceListUpdate() { final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices); - Collections.sort(mediaDevices, COMPARATOR); for (DeviceCallback callback : getCallbacks()) { callback.onDeviceListUpdate(mediaDevices); } @@ -472,6 +471,7 @@ public class LocalMediaManager implements BluetoothCallback { synchronized (mMediaDevicesLock) { mMediaDevices.clear(); mMediaDevices.addAll(devices); + Collections.sort(devices, COMPARATOR); // Add disconnected bluetooth devices only when phone output device is available. for (MediaDevice device : devices) { final int type = device.getDeviceType(); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java index 647fd2acf7c8..552fa11a42b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java @@ -39,8 +39,7 @@ public class MediaOutputConstants { /** * A string extra specifying a media package name. */ - public static final String EXTRA_PACKAGE_NAME = - "com.android.settings.panel.extra.PACKAGE_NAME"; + public static final String EXTRA_PACKAGE_NAME = "package_name"; /** * An intent action to launch media output dialog. diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 259484073162..71e09106368b 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -84,8 +84,6 @@ <uses-permission android:name="android.permission.READ_INPUT_STATE" /> <uses-permission android:name="android.permission.SET_ORIENTATION" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> - <!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient --> - <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" /> <uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" /> <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" /> @@ -125,6 +123,8 @@ <uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> + <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" /> + <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" /> <uses-permission android:name="android.permission.QUERY_USERS" /> <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" /> <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/> @@ -398,6 +398,7 @@ <!-- Permission required for CTS to test sensor privacy behavior --> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- Permission needed for CTS test - CallLogTest --> <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" /> diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS index 34901f5830c4..6d738f8d4d43 100644 --- a/packages/Shell/OWNERS +++ b/packages/Shell/OWNERS @@ -9,3 +9,4 @@ yamasani@google.com toddke@google.com cbrubaker@google.com omakoto@google.com +michaelwr@google.com diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index fbe58c505662..044216e5423d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -87,6 +87,7 @@ <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- ActivityManager --> <uses-permission android:name="android.permission.REAL_GET_TASKS" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml index 71cdaf5c7091..79868093fb12 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml @@ -24,7 +24,7 @@ <include style="@style/BouncerSecurityContainer" layout="@layout/keyguard_host_view" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml index c75ee51517d1..04e645bd0a32 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml @@ -41,14 +41,13 @@ android:layout_gravity="center"> <com.android.keyguard.KeyguardSecurityViewFlipper android:id="@+id/view_flipper" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:paddingTop="@dimen/keyguard_security_view_top_margin" android:paddingStart="@dimen/keyguard_security_view_lateral_margin" android:paddingEnd="@dimen/keyguard_security_view_lateral_margin" - android:layout_gravity="center" android:gravity="center"> </com.android.keyguard.KeyguardSecurityViewFlipper> </com.android.keyguard.KeyguardSecurityContainer> diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index 6176f7c1dd0a..8d9d6ee68c67 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,5 +22,4 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> - <bool name="can_use_one_handed_bouncer">false</bool> </resources> diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml index d895dd0c85c7..2165b12e5697 100644 --- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml +++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml @@ -26,12 +26,4 @@ <corners android:radius="@dimen/control_corner_radius" /> </shape> </item> - <item> - <shape> - <stroke - android:width="1dp" - android:color="#4DFFFFFF" /> - <corners android:radius="@dimen/control_corner_radius"/> - </shape> - </item> </ripple> diff --git a/packages/SystemUI/res/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml index cb4686dd04a7..1ccb176b8689 100644 --- a/packages/SystemUI/res/drawable/controls_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/controls_dialog_bg.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="?android:attr/colorBackgroundFloating" /> + <solid android:color="?android:attr/colorBackground" /> <corners android:radius="@dimen/notification_corner_radius" /> </shape> diff --git a/packages/SystemUI/res/drawable/horizontal_ellipsis.xml b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml new file mode 100644 index 000000000000..1800857a826c --- /dev/null +++ b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorBackgroundFloating" > + + <path + android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" + android:fillColor="@android:color/white" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml index 9f66581d8053..96e01934e670 100644 --- a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml +++ b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml @@ -36,8 +36,4 @@ </vector> </item> - <item> - <ripple android:color="@color/GM2_grey_600"></ripple> - </item> - </layer-list> diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_window.xml b/packages/SystemUI/res/drawable/ic_open_in_new_window.xml index 659b02048c73..368c8dfb5023 100644 --- a/packages/SystemUI/res/drawable/ic_open_in_new_window.xml +++ b/packages/SystemUI/res/drawable/ic_open_in_new_window.xml @@ -50,28 +50,7 @@ android:strokeWidth="1" android:pathData="M 12.5 13.92 L 15.59 17 L 17 15.59 L 13.91 12.5 L 16.5 12.5 L 16.5 10.5 L 10.5 10.5 L 10.5 16.5 L 12.5 16.5 Z" /> </group> - <group - android:translateX="22.000000" - android:translateY="2.000000"> - <group - android:translateX="3.000000" - android:translateY="3.000000"> - <group - android:translateX="-3.000000" - android:translateY="-3.000000"> - <path - android:fillColor="#80868B" - android:fillType="evenOdd" - android:strokeWidth="1" - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - </group> - </group> - </group> </vector> </item> - <item> - <ripple android:color="@color/GM2_grey_600"></ripple> - </item> - </layer-list> diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res/drawable/volume_drawer_bg.xml index e09bf7e37ed0..f0e22926d07a 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml +++ b/packages/SystemUI/res/drawable/volume_drawer_bg.xml @@ -14,7 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<resources> - <bool name="can_use_one_handed_bouncer">true</bool> -</resources> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <size android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_tile_label_divider.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml index 150a5b8bfef1..5e7cb12d1c5f 100644 --- a/packages/SystemUI/res/layout/qs_tile_label_divider.xml +++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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,5 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<View />
\ No newline at end of file +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <size + android:height="@dimen/volume_ringer_drawer_item_size" + android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml new file mode 100644 index 000000000000..b0e0ed5079e6 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -0,0 +1,56 @@ +<?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. + --> + +<!-- SeekBar drawable for volume rows. This contains a background layer (with a solid round rect, + and a bottom-aligned icon) and a progress layer (with an accent-colored round rect and icon) + that moves up and down with the progress value. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <item android:id="@android:id/background" + android:gravity="center_vertical|fill_horizontal"> + <layer-list> + <item android:id="@+id/volume_seekbar_background_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half" /> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_background_icon" + android:gravity="center_vertical|left" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:left="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <!-- A placeholder drawable is required here - it'll be replaced in code. --> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@drawable/ic_volume_media" + android:tint="?android:attr/colorAccent" /> + </rotate> + </item> + </layer-list> + </item> + <item android:id="@android:id/progress" + android:gravity="center_vertical|fill_horizontal"> + <com.android.systemui.util.RoundedCornerProgressDrawable + android:drawable="@drawable/volume_row_seekbar_progress" + /> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml new file mode 100644 index 000000000000..ef202360b1ce --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml @@ -0,0 +1,44 @@ +<?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. + --> + +<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up + and down as the progress value changes. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true"> + <item android:id="@+id/volume_seekbar_progress_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half"/> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_progress_icon" + android:gravity="center_vertical|right" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:right="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <!-- A placeholder drawable is required here - it'll be replaced in code. --> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@drawable/ic_volume_media" + android:tint="?android:attr/colorBackgroundFloating" /> + </rotate> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index c420117073c5..237dc02e5d8c 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -32,105 +32,124 @@ android:gravity="right" android:layout_gravity="right" android:background="@android:color/transparent" - android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" + android:paddingRight="@dimen/volume_dialog_stream_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:clipToPadding="false"> - <FrameLayout - android:id="@+id/ringer" - android:layout_width="@dimen/volume_dialog_ringer_size" - android:layout_height="@dimen/volume_dialog_ringer_size" - android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:gravity="right" - android:layout_gravity="right" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="fitCenter" - android:padding="@dimen/volume_dialog_ringer_icon_padding" - android:tint="@color/accent_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - - <include layout="@layout/volume_dnd_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_dialog_stream_padding" - android:layout_marginTop="6dp"/> - </FrameLayout> - + <!-- + Container for a) the ringer drawer and the caption button next to b) the volume rows. + --> <LinearLayout - android:id="@+id/main" android:layout_width="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" android:layout_height="wrap_content" - android:layout_marginTop="68dp" - android:gravity="right" - android:layout_gravity="right" - android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" + android:orientation="horizontal" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > - <LinearLayout - android:id="@+id/volume_dialog_rows" + android:clipToPadding="false"> + + <!-- The ringer drawer and the caption button. --> + <FrameLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" - android:gravity="center" - android:orientation="horizontal" + android:layout_height="match_parent" android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <include layout="@layout/volume_ringer_drawer" + android:layout_gravity="top|right"/> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:gravity="center" + android:layout_gravity="bottom|right" + android:layout_marginBottom="@dimen/volume_dialog_tap_target_size" + android:clipToPadding="false"> + + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="@color/caption_tint_color_selector" + android:layout_gravity="center" + android:soundEffectsEnabled="false" + sysui:optedOut="false"/> + + </FrameLayout> + + </FrameLayout> + <FrameLayout - android:id="@+id/settings_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:visibility="gone" + android:id="@+id/ringer" + android:layout_width="@dimen/volume_dialog_ringer_size" + android:layout_height="@dimen/volume_dialog_ringer_size" + android:layout_marginBottom="@dimen/volume_dialog_spacer" + android:gravity="right" + android:layout_gravity="right" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipToPadding="false" + android:background="@drawable/rounded_bg_full"> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" + android:id="@+id/ringer_icon" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:padding="@dimen/volume_dialog_ringer_icon_padding" + android:tint="@color/accent_tint_color_selector" android:layout_gravity="center" - android:contentDescription="@string/accessibility_volume_settings" - android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorSecondary" android:soundEffectsEnabled="false" /> + + <include layout="@layout/volume_dnd_icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/volume_dialog_stream_padding" + android:layout_marginTop="6dp"/> </FrameLayout> - </LinearLayout> - <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginRight="68dp" - android:gravity="right" - android:layout_gravity="right" - android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" - android:background="@drawable/rounded_bg_full"> - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="@color/caption_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" - sysui:optedOut="false"/> - </FrameLayout> + <LinearLayout + android:id="@+id/main" + android:layout_width="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false"> + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:gravity="center" + android:orientation="horizontal"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + <FrameLayout + android:id="@+id/settings_container" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/settings" + android:src="@drawable/horizontal_ellipsis" + android:layout_width="@dimen/volume_dialog_tap_target_size" + android:layout_height="@dimen/volume_dialog_tap_target_size" + android:layout_gravity="center" + android:contentDescription="@string/accessibility_volume_settings" + android:background="@drawable/ripple_drawable_20dp" + android:tint="?android:attr/colorBackgroundFloating" + android:soundEffectsEnabled="false" /> + </FrameLayout> + </LinearLayout> + + </LinearLayout> <ViewStub android:id="@+id/odi_captions_tooltip_stub" diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index 73beefc9da83..d996cee4b39e 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -32,7 +32,8 @@ android:id="@+id/header_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"/> + android:paddingEnd="@dimen/media_output_dialog_header_icon_padding" + android:importantForAccessibility="no"/> <LinearLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 0822947e8b16..93dd1a12f147 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -81,6 +81,22 @@ android:layout_height="match_parent" android:layout_weight="@integer/qs_footer_actions_weight" android:gravity="center_vertical|end" > + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/pm_lite" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:clipToPadding="false" + android:contentDescription="@string/accessibility_quick_settings_edit" + android:focusable="true" + android:padding="@dimen/qs_footer_icon_padding" + android:src="@*android:drawable/ic_lock_power_off" + android:tint="?android:attr/colorForeground" + android:visibility="gone" + /> + <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" android:layout_width="@dimen/qs_footer_action_button_size" diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml index ee54f1da8897..c83077371bb0 100644 --- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml +++ b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml @@ -14,4 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<include layout="@layout/qs_paged_page" /> +<com.android.systemui.qs.SideLabelTileLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tile_page" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 571cbbcc77db..1ce87c1c0236 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -20,7 +20,6 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" - android:minHeight="48dp" android:paddingTop="12dp"> <LinearLayout android:id="@+id/label_group" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 23f34251b812..bbb6107d149e 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -37,12 +37,6 @@ android:layout_height="match_parent" android:layout_width="match_parent" /> - <ViewStub - android:id="@+id/keyguard_user_switcher_stub" - android:layout="@layout/keyguard_user_switcher" - android:layout_height="match_parent" - android:layout_width="match_parent" /> - <include layout="@layout/keyguard_status_view" android:visibility="gone" /> @@ -61,12 +55,14 @@ android:id="@+id/qs_frame" android:layout="@layout/qs_panel" android:layout_width="@dimen/qs_panel_width" - android:layout_height="match_parent" + android:layout_height="0dp" android:clipToPadding="false" android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" systemui:layout_constraintStart_toStartOf="parent" systemui:layout_constraintEnd_toEndOf="parent" + systemui:layout_constraintTop_toTopOf="parent" + systemui:layout_constraintBottom_toBottomOf="parent" /> <androidx.constraintlayout.widget.Guideline @@ -109,5 +105,11 @@ layout="@layout/keyguard_bottom_area" android:visibility="gone" /> + <ViewStub + android:id="@+id/keyguard_user_switcher_stub" + android:layout="@layout/keyguard_user_switcher" + android:layout_height="match_parent" + android:layout_width="match_parent" /> + <include layout="@layout/status_bar_expanded_plugin_frame"/> </com.android.systemui.statusbar.phone.NotificationPanelView> diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 1810c196c83d..6aac5a34821b 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -21,6 +21,8 @@ android:layout_height="wrap_content" android:gravity="right" android:layout_gravity="right" + android:paddingRight="@dimen/volume_dialog_stream_padding" + android:clipToPadding="false" android:background="@android:color/transparent" android:theme="@style/volume_dialog_theme"> @@ -34,13 +36,15 @@ android:layout_gravity="right" android:background="@android:color/transparent" android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:orientation="vertical" - android:clipToPadding="false"> + android:clipToPadding="false" + android:clipChildren="false"> + + <include layout="@layout/volume_ringer_drawer" /> <FrameLayout + android:visibility="gone" android:id="@+id/ringer" android:layout_width="@dimen/volume_dialog_ringer_size" android:layout_height="@dimen/volume_dialog_ringer_size" @@ -77,10 +81,8 @@ android:gravity="right" android:layout_gravity="right" android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > + android:clipToPadding="false" > <LinearLayout android:id="@+id/volume_dialog_rows" android:layout_width="wrap_content" @@ -88,24 +90,22 @@ android:minWidth="@dimen/volume_dialog_panel_width" android:gravity="center" android:orientation="horizontal" - android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> + android:layout_marginTop="@dimen/volume_row_slider_padding_start"> <!-- volume rows added and removed here! :-) --> </LinearLayout> <FrameLayout android:id="@+id/settings_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:layout_height="wrap_content"> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" + android:src="@drawable/horizontal_ellipsis" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" android:contentDescription="@string/accessibility_volume_settings" android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorBackgroundFloating" android:soundEffectsEnabled="false" /> </FrameLayout> </LinearLayout> @@ -118,7 +118,6 @@ android:gravity="right" android:layout_gravity="right" android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" android:background="@drawable/rounded_bg_full"> <com.android.systemui.volume.CaptionsToggleImageButton android:id="@+id/odi_captions_icon" @@ -127,7 +126,7 @@ android:background="@drawable/rounded_ripple" android:layout_width="match_parent" android:layout_height="match_parent" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorAccent" android:layout_gravity="center" android:soundEffectsEnabled="false" sysui:optedOut="false"/> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index b9efc5be70c1..fda59b50104a 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -20,11 +20,12 @@ android:layout_width="@dimen/volume_dialog_panel_width" android:clipChildren="false" android:clipToPadding="false" + android:translationZ="@dimen/volume_dialog_elevation" android:theme="@style/volume_dialog_theme"> <LinearLayout android:layout_height="wrap_content" - android:layout_width="match_parent" + android:layout_width="@dimen/volume_dialog_panel_width" android:gravity="center" android:layout_gravity="center" android:orientation="vertical" > @@ -41,21 +42,23 @@ <FrameLayout android:id="@+id/volume_row_slider_frame" android:layout_width="match_parent" - android:layout_marginTop="@dimen/volume_dialog_slider_margin_top" - android:layout_marginBottom="@dimen/volume_dialog_slider_margin_bottom" - android:layoutDirection="rtl" - android:layout_height="@dimen/volume_dialog_slider_height"> + android:layout_height="@dimen/volume_row_slider_height"> + <include layout="@layout/volume_dnd_icon"/> <SeekBar android:id="@+id/volume_row_slider" + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:paddingStart="0dp" + android:paddingEnd="0dp" android:clickable="true" - android:layout_width="@dimen/volume_dialog_slider_height" + android:layout_width="@dimen/volume_row_slider_height" android:layout_height="match_parent" - android:layoutDirection="rtl" android:layout_gravity="center" - android:rotation="90" /> + android:rotation="270" /> </FrameLayout> <com.android.keyguard.AlphaOptimizedImageButton + android:visibility="gone" android:id="@+id/volume_row_icon" style="@style/VolumeButtons" android:layout_width="@dimen/volume_dialog_tap_target_size" @@ -66,6 +69,4 @@ android:soundEffectsEnabled="false" /> </LinearLayout> - <include layout="@layout/volume_dnd_icon"/> - </FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml new file mode 100644 index 000000000000..d6e1782382fa --- /dev/null +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -0,0 +1,126 @@ +<?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. +--> + +<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:clipToPadding="false" + android:clipChildren="false"> + + <!-- Drawer view, invisible by default. --> + <FrameLayout + android:id="@+id/volume_drawer_container" + android:alpha="0.0" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/volume_drawer_bg" + android:orientation="vertical"> + + <!-- View that is animated to a tapped ringer selection, so it appears selected. --> + <FrameLayout + android:id="@+id/volume_drawer_selection_background" + android:alpha="0.0" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:background="@drawable/volume_drawer_selection_bg" /> + + <LinearLayout + android:id="@+id/volume_drawer_options" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/volume_drawer_vibrate" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_vibrate" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_vibrate_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_vibrate" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_mute" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_mute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_mute_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_mute" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_normal" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_unmute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_normal_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer" /> + + </FrameLayout> + + </LinearLayout> + + </FrameLayout> + + <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding + position in the drawer. When the drawer is closed, it animates back. --> + <FrameLayout + android:id="@+id/volume_new_ringer_active_icon_container" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:description="@string/volume_ringer_change" + android:background="@drawable/volume_drawer_selection_bg"> + + <ImageView + android:id="@+id/volume_new_ringer_active_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorBackgroundFloating" + android:src="@drawable/ic_volume_media" /> + + </FrameLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index acd671cb6297..3bc1c8053db3 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -265,7 +265,6 @@ <color name="control_enabled_cool_foreground">@color/GM2_blue_300</color> <color name="control_thumbnail_tint">#33000000</color> <color name="control_thumbnail_shadow_color">@*android:color/black</color> - <color name="controls_lockscreen_scrim">#AA000000</color> <!-- Docked misalignment message --> <color name="misalignment_text_color">#F28B82</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 13c01102d032..0893c1488005 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,6 +91,9 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> + <!-- The number of columns in the Quick Settings customizer --> + <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> + <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> @@ -421,6 +424,9 @@ vibrator is capable of subtle vibrations --> <bool name="config_vibrateOnIconAnimation">false</bool> + <!-- Adjust the theme on fully custom and decorated custom view notifications --> + <bool name="config_adjustThemeOnNotificationCustomViews">false</bool> + <!-- If true, enable the advance anti-falsing classifier on the lockscreen. On some devices it does not work well, particularly with noisy touchscreens. Note that disabling it may increase the rate of unintentional unlocks. --> @@ -574,4 +580,7 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">false</bool> + + <!-- Determines whether the shell features all run on another thread. --> + <bool name="config_enableShellMainThread">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4bc5a300e833..ea0ea5e9472a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -456,10 +456,12 @@ <dimen name="volume_dialog_panel_transparent_padding">20dp</dimen> - <dimen name="volume_dialog_stream_padding">8dp</dimen> + <dimen name="volume_dialog_stream_padding">12dp</dimen> <dimen name="volume_dialog_panel_width">64dp</dimen> + <dimen name="volume_dialog_panel_width_half">32dp</dimen> + <dimen name="volume_dialog_slider_height">116dp</dimen> <dimen name="volume_dialog_ringer_size">64dp</dimen> @@ -486,6 +488,13 @@ <dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen> + <!-- Size of each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_item_size">64dp</dimen> + <dimen name="volume_ringer_drawer_item_size_half">32dp</dimen> + + <!-- Size of the icon inside each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_icon_size">24dp</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -651,7 +660,7 @@ </dimen> <!-- The height of a notification header --> - <dimen name="notification_header_height">53dp</dimen> + <dimen name="notification_header_height">@*android:dimen/notification_header_height</dimen> <!-- The height of the gap between adjacent notification sections. --> <dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen> @@ -966,7 +975,7 @@ <dimen name="volume_row_padding_start">4dp</dimen> <dimen name="volume_row_header_padding_start">16dp</dimen> <dimen name="volume_row_height">64dp</dimen> - <dimen name="volume_row_slider_height">48dp</dimen> + <dimen name="volume_row_slider_height">192dp</dimen> <dimen name="volume_row_slider_padding_start">12dp</dimen> <dimen name="volume_expander_margin_end">2dp</dimen> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index d4bb128120e9..e5518928c98c 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -42,4 +42,8 @@ <bool name="flag_lockscreen_animations">false</bool> <bool name="flag_toast_style">false</bool> + + <bool name="flag_navigation_bar_overlay">false</bool> + + <bool name="flag_pm_lite">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4b95c16a3602..0414de6eb7ca 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -959,7 +959,7 @@ <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] --> <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string> <!-- QuickSettings: Label for the toggle that controls whether Reduce Brightness is enabled. [CHAR LIMIT=NONE] --> - <string name="quick_settings_reduce_bright_colors_label">Reduce Brightness</string> + <string name="quick_settings_reduce_bright_colors_label">Reduce brightness</string> <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_label">NFC</string> @@ -1558,6 +1558,8 @@ <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string> <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string> + <string name="volume_ringer_change">Tap to change ringer mode</string> + <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] --> <string name="volume_ringer_hint_mute">mute</string> <!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 14b376a8bf6c..2d202fb45bbc 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -663,16 +663,16 @@ </style> <style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> - <item name="android:windowAnimationStyle">@style/Animation.Fade</item> + <item name="android:windowAnimationStyle">@style/Animation.ControlDialog</item> <item name="android:windowFullscreen">true</item> <item name="android:windowIsFloating">false</item> - <item name="android:windowBackground">@color/controls_lockscreen_scrim</item> + <item name="android:windowBackground">@null</item> <item name="android:backgroundDimEnabled">true</item> </style> - <style name="Animation.Fade"> - <item name="android:windowEnterAnimation">@android:anim/fade_in</item> - <item name="android:windowExitAnimation">@android:anim/fade_out</item> + <style name="Animation.ControlDialog"> + <item name="android:windowEnterAnimation">@*android:anim/dialog_enter</item> + <item name="android:windowExitAnimation">@*android:anim/dialog_exit</item> </style> <style name="Control" /> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index d2698ee9fe3b..b3a29a3fec78 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -190,7 +190,7 @@ public class TaskStackChangeListeners { } @Override - public void onActivityDismissingDockedStack() { + public void onActivityDismissingDockedTask() { mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f511ed1c69c4..5f6fd30ffa1b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -29,17 +29,12 @@ import android.app.AlertDialog; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; -import android.provider.Settings; import android.util.AttributeSet; import android.util.MathUtils; import android.util.TypedValue; -import android.view.Gravity; import android.view.MotionEvent; -import android.view.OrientationEventListener; import android.view.VelocityTracker; -import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewPropertyAnimator; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimationControlListener; @@ -60,7 +55,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.List; @@ -105,12 +99,6 @@ public class KeyguardSecurityContainer extends FrameLayout { private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; - private boolean mIsSecurityViewLeftAligned = true; - private boolean mOneHandedMode = false; - private SecurityMode mSecurityMode = SecurityMode.Invalid; - private ViewPropertyAnimator mRunningOneHandedAnimator; - private final OrientationEventListener mOrientationEventListener; - private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -169,20 +157,16 @@ public class KeyguardSecurityContainer extends FrameLayout { // Used to notify the container when something interesting happens. public interface SecurityCallback { boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); - void userActivity(); - void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** - * @param strongAuth wheher the user has authenticated with strong authentication like - * pattern, password or PIN but not by trust agents or fingerprint + * @param strongAuth wheher the user has authenticated with strong authentication like + * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ void finish(boolean strongAuth, int targetUserId); - void reset(); - void onCancelClicked(); } @@ -240,136 +224,12 @@ public class KeyguardSecurityContainer extends FrameLayout { super(context, attrs, defStyle); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); mViewConfiguration = ViewConfiguration.get(context); - - mOrientationEventListener = new OrientationEventListener(context) { - @Override - public void onOrientationChanged(int orientation) { - updateLayoutForSecurityMode(mSecurityMode); - } - }; } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { - mSecurityMode = securityMode; mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); updateBiometricRetry(securityMode, faceAuthEnabled); - updateLayoutForSecurityMode(securityMode); - mOrientationEventListener.enable(); - } - - void updateLayoutForSecurityMode(SecurityMode securityMode) { - mSecurityMode = securityMode; - mOneHandedMode = canUseOneHandedBouncer(); - - if (mOneHandedMode) { - mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext); - } - - updateSecurityViewGravity(); - updateSecurityViewLocation(false); - } - - /** Return whether the one-handed keyguard should be enabled. */ - private boolean canUseOneHandedBouncer() { - // Is it enabled? - if (!getResources().getBoolean( - com.android.internal.R.bool.config_enableOneHandedKeyguard)) { - return false; - } - - if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) { - return false; - } - - return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); - } - - /** Read whether the one-handed keyguard should be on the left/right from settings. */ - private boolean isOneHandedKeyguardLeftAligned(Context context) { - try { - return Settings.Global.getInt(context.getContentResolver(), - Settings.Global.ONE_HANDED_KEYGUARD_SIDE) - == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; - } catch (Settings.SettingNotFoundException ex) { - return true; - } - } - - private void updateSecurityViewGravity() { - View securityView = findKeyguardSecurityView(); - - if (securityView == null) { - return; - } - - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams(); - - if (mOneHandedMode) { - lp.gravity = Gravity.LEFT | Gravity.BOTTOM; - } else { - lp.gravity = Gravity.CENTER_HORIZONTAL; - } - - securityView.setLayoutParams(lp); - } - - /** - * Moves the inner security view to the correct location (in one handed mode) with animation. - * This is triggered when the user taps on the side of the screen that is not currently occupied - * by the security view . - */ - private void updateSecurityViewLocation(boolean animate) { - View securityView = findKeyguardSecurityView(); - - if (securityView == null) { - return; - } - - if (!mOneHandedMode) { - securityView.setTranslationX(0); - return; - } - - if (mRunningOneHandedAnimator != null) { - mRunningOneHandedAnimator.cancel(); - mRunningOneHandedAnimator = null; - } - - int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f); - - if (animate) { - mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation); - mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRunningOneHandedAnimator = null; - } - }); - - mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - mRunningOneHandedAnimator.start(); - } else { - securityView.setTranslationX(targetTranslation); - } - } - - @Nullable - private KeyguardSecurityViewFlipper findKeyguardSecurityView() { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - - if (isKeyguardSecurityView(child)) { - return (KeyguardSecurityViewFlipper) child; - } - } - - return null; - } - - private boolean isKeyguardSecurityView(View view) { - return view instanceof KeyguardSecurityViewFlipper; } public void onPause() { @@ -378,7 +238,6 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); - mOrientationEventListener.disable(); } @Override @@ -460,44 +319,19 @@ public class KeyguardSecurityContainer extends FrameLayout { if (mSwipeListener != null) { mSwipeListener.onSwipeUp(); } - } else { - if (!mIsDragging) { - handleTap(event); - } } } return true; } - private void handleTap(MotionEvent event) { - // If we're using a fullscreen security mode, skip - if (!mOneHandedMode) { - return; - } - - // Did the tap hit the "other" side of the bouncer? - if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f)) - || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) { - mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; - - Settings.Global.putInt( - mContext.getContentResolver(), - Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT - : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); - - updateSecurityViewLocation(true); - } - } - void setSwipeListener(SwipeListener swipeListener) { mSwipeListener = swipeListener; } private void startSpringAnimation(float startVelocity) { mSpringAnimation - .setStartVelocity(startVelocity) - .animateToFinalPosition(0); + .setStartVelocity(startVelocity) + .animateToFinalPosition(0); } public void startDisappearAnimation(SecurityMode securitySelection) { @@ -607,17 +441,18 @@ public class KeyguardSecurityContainer extends FrameLayout { return insets.inset(0, 0, 0, inset); } + private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } mAlertDialog = new AlertDialog.Builder(mContext) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setNeutralButton(R.string.ok, null) - .create(); + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.ok, null) + .create(); if (!(mContext instanceof Activity)) { mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } @@ -655,44 +490,6 @@ public class KeyguardSecurityContainer extends FrameLayout { } } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int maxHeight = 0; - int maxWidth = 0; - int childState = 0; - - int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(widthMeasureSpec) / 2, - MeasureSpec.getMode(widthMeasureSpec)); - - for (int i = 0; i < getChildCount(); i++) { - final View view = getChildAt(i); - if (view.getVisibility() != GONE) { - if (mOneHandedMode && isKeyguardSecurityView(view)) { - measureChildWithMargins(view, halfWidthMeasureSpec, 0, - heightMeasureSpec, 0); - } else { - measureChildWithMargins(view, widthMeasureSpec, 0, - heightMeasureSpec, 0); - } - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - maxWidth = Math.max(maxWidth, - view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); - maxHeight = Math.max(maxHeight, - view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); - childState = combineMeasuredStates(childState, view.getMeasuredState()); - } - } - - // Check against our minimum height and width - maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); - maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); - - setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), - resolveSizeAndState(maxHeight, heightMeasureSpec, - childState << MEASURED_HEIGHT_STATE_SHIFT)); - } - void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index fdab8db67431..1a8d420fb394 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -404,7 +404,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (newView != null) { newView.onResume(KeyguardSecurityView.VIEW_REVEALED); mSecurityViewFlipperController.show(newView); - mView.updateLayoutForSecurityMode(securityMode); } mSecurityCallback.onSecurityModeChanged( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index 631c24844417..c77c86711abf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -92,13 +92,4 @@ public class KeyguardSecurityModel { throw new IllegalStateException("Unknown security quality:" + security); } } - - /** - * Returns whether the given security view should be used in a "one handed" way. This can be - * used to change how the security view is drawn (e.g. take up less of the screen, and align to - * one side). - */ - public static boolean isSecurityViewOneHanded(SecurityMode securityMode) { - return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN; - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index d9a1eb6c0b28..a4054bea1167 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -305,6 +305,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLogoutEnabled; // cached value to avoid IPCs private boolean mIsUdfpsEnrolled; + private boolean mKeyguardQsUserSwitchEnabled; // If the user long pressed the lock icon, disabling face auth for the current session. private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -1916,7 +1917,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser()) && !isUdfpsEnrolled(); } - return true; + return !isKeyguardQsUserSwitchEnabled(); } /** @@ -1926,6 +1927,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsUdfpsEnrolled; } + /** + * @return true if the keyguard qs user switcher shortcut is enabled + */ + public boolean isKeyguardQsUserSwitchEnabled() { + return mKeyguardQsUserSwitchEnabled; + } + + public void setKeyguardQsUserSwitchEnabled(boolean enabled) { + mKeyguardQsUserSwitchEnabled = enabled; + } + private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) { diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 8cb1bc4878a5..5db3349b646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -69,7 +69,9 @@ public class NumPadButton extends AlphaOptimizedImageButton { @Override public boolean onTouchEvent(MotionEvent event) { - if (mAnimator != null) mAnimator.start(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (mAnimator != null) mAnimator.start(); + } return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index a4a781dc6ff5..e6a9d4fdd547 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -160,10 +160,9 @@ public class NumPadKey extends ViewGroup { public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); + if (mAnimator != null) mAnimator.start(); } - if (mAnimator != null) mAnimator.start(); - return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 9686c91f2c31..79f0688db869 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; @@ -137,8 +137,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAudioManager = audioManager; mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); @@ -159,8 +159,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); @@ -583,16 +583,16 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { - if (sensor == INDIVIDUAL_SENSOR_CAMERA) { + if (sensor == CAMERA) { mCameraDisabled = blocked; - } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { + } else if (sensor == MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt index cad166d7cd9e..1ea1d97cace5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt @@ -41,7 +41,7 @@ import com.android.systemui.controls.ui.ControlsUiController object ControlsAnimations { - private const val ALPHA_EXIT_DURATION = 167L + private const val ALPHA_EXIT_DURATION = 183L private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 1c2f17c55671..2d647a907b17 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -313,6 +313,10 @@ class ControlsFavoritingActivity @Inject constructor( setOnClickListener { val i = Intent().apply { component = ComponentName(context, ControlsProviderSelectorActivity::class.java) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } if (doneButton.isEnabled) { // The user has made changes diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 08147746a4c8..d5e41d031eac 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -32,6 +32,8 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent @@ -49,13 +51,15 @@ class ControlsProviderSelectorActivity @Inject constructor( private val listingController: ControlsListingController, private val controlsController: ControlsController, private val globalActionsComponent: GlobalActionsComponent, - broadcastDispatcher: BroadcastDispatcher + private val broadcastDispatcher: BroadcastDispatcher, + private val uiController: ControlsUiController ) : LifecycleActivity() { companion object { private const val TAG = "ControlsProviderSelectorActivity" } + private var backToGlobalActions = true private lateinit var recyclerView: RecyclerView private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = listingController.currentUserId @@ -101,10 +105,19 @@ class ControlsProviderSelectorActivity @Inject constructor( } } requireViewById<View>(R.id.done).visibility = View.GONE + + backToGlobalActions = intent.getBooleanExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + true + ) } override fun onBackPressed() { - globalActionsComponent.handleShowGlobalActionsMenu() + if (backToGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + } animateExitAndFinish() } @@ -152,8 +165,13 @@ class ControlsProviderSelectorActivity @Inject constructor( listingController.getAppLabel(it)) putExtra(Intent.EXTRA_COMPONENT_NAME, it) putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) + animateExitAndFinish() } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d4678f39e404..127128dda112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -103,6 +103,11 @@ public class KeyguardIndicationRotateTextViewController extends */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean showImmediately) { + if (type == INDICATION_TYPE_NOW_PLAYING + || type == INDICATION_TYPE_REVERSE_CHARGING) { + // temporarily don't show here, instead use AmbientContainer b/181049781 + return; + } final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 27ea64f85b11..70b7b047eebc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -25,7 +25,6 @@ import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -54,7 +53,6 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -116,7 +114,7 @@ public class NavigationBarController implements Callbacks, // Tracks config changes that will actually recreate the nav bar private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS + | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_UI_MODE); @Inject @@ -171,6 +169,7 @@ public class NavigationBarController implements Callbacks, configurationController.addCallback(this); mConfigChanges.applyNewConfig(mContext.getResources()); mNavBarOverlayController = navBarOverlayController; + mNavigationModeController.addListener(this); } @Override @@ -188,17 +187,18 @@ public class NavigationBarController implements Callbacks, @Override public void onNavigationModeChanged(int mode) { - // Workaround for b/132825155, for secondary users, we currently don't receive configuration - // changes on overlay package change since SystemUI runs for the system user. In this case, - // trigger a new configuration change to ensure that the nav bar is updated in the same way. - int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); - if (userId != UserHandle.USER_SYSTEM) { - mHandler.post(() -> { - for (int i = 0; i < mNavigationBars.size(); i++) { - recreateNavigationBar(mNavigationBars.keyAt(i)); + mHandler.post(() -> { + for (int i = 0; i < mNavigationBars.size(); i++) { + NavigationBar navBar = mNavigationBars.valueAt(i); + if (navBar == null) { + continue; } - }); - } + NavigationBarView view = (NavigationBarView) mNavigationBars.get(i).getView(); + if (view != null) { + view.updateStates(); + } + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java index c526c5d59552..62b9458447d8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarOverlayController.java @@ -21,6 +21,7 @@ import android.content.Context; import android.view.View; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.FeatureFlags; import java.util.function.Consumer; @@ -31,16 +32,22 @@ import javax.inject.Inject; public class NavigationBarOverlayController { protected final Context mContext; + protected final FeatureFlags mFeatureFlags; @Inject - public NavigationBarOverlayController(Context context) { + public NavigationBarOverlayController(Context context, FeatureFlags featureFlags) { mContext = context; + mFeatureFlags = featureFlags; } public Context getContext() { return mContext; } + public boolean isNavigationBarOverlayEnabled() { + return mFeatureFlags.isNavigationBarOverlayEnabled(); + } + /** * Initialize the controller with visibility change callback and light/dark icon color. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index b55fa4d612f9..61e1d61e7909 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -192,6 +192,7 @@ public final class NavigationBarTransitions extends BarTransitions implements buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity); } mView.getRotationButtonController().setDarkIntensity(darkIntensity); + Dependency.get(NavigationBarOverlayController.class).setDarkIntensity(darkIntensity); for (DarkIntensityListener listener : mDarkIntensityListeners) { listener.onDarkIntensity(darkIntensity); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 8e75bec72c15..19e32783f765 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -171,6 +171,7 @@ public class NavigationBarView extends FrameLayout implements private NotificationPanelViewController mPanelView; private FloatingRotationButton mFloatingRotationButton; private RotationButtonController mRotationButtonController; + private NavigationBarOverlayController mNavBarOverlayController; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -339,8 +340,11 @@ public class NavigationBarView extends FrameLayout implements isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton, mRotationButtonListener); - Dependency.get(NavigationBarOverlayController.class).init( - mNavbarOverlayVisibilityChangeCallback, mLightIconColor, mDarkIconColor); + mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.init( + mNavbarOverlayVisibilityChangeCallback, mLightIconColor, mDarkIconColor); + } mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); @@ -431,8 +435,9 @@ public class NavigationBarView extends FrameLayout implements // The visibility of the navigation bar buttons is dependent on the transient state of // the navigation bar. - Dependency.get(NavigationBarOverlayController.class).setButtonState( - isTransient, /* force */ false); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.setButtonState(isTransient, /* force */ false); + } } void onBarTransition(int newMode) { @@ -666,7 +671,9 @@ public class NavigationBarView extends FrameLayout implements } mImeVisible = visible; mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible); - Dependency.get(NavigationBarOverlayController.class).setCanShow(!mImeVisible); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.setCanShow(!mImeVisible); + } } public void setDisabledFlags(int disabledFlags) { @@ -999,10 +1006,9 @@ public class NavigationBarView extends FrameLayout implements } else { updateButtonLocation(getRotateSuggestionButton(), inScreenSpace); } - final NavigationBarOverlayController navBarButtonsController = - Dependency.get(NavigationBarOverlayController.class); - if (navBarButtonsController.isVisible()) { - updateButtonLocation(navBarButtonsController.getCurrentView(), inScreenSpace); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled() + && mNavBarOverlayController.isVisible()) { + updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace); } return mTmpRegion; } @@ -1230,7 +1236,9 @@ public class NavigationBarView extends FrameLayout implements if (mRotationButtonController != null) { mRotationButtonController.registerListeners(); } - Dependency.get(NavigationBarOverlayController.class).registerListeners(); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.registerListeners(); + } getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); updateNavButtonIcons(); @@ -1247,7 +1255,10 @@ public class NavigationBarView extends FrameLayout implements if (mRotationButtonController != null) { mRotationButtonController.unregisterListeners(); } - Dependency.get(NavigationBarOverlayController.class).unregisterListeners(); + + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.unregisterListeners(); + } mEdgeBackGestureHandler.onNavBarDetached(); getViewTreeObserver().removeOnComputeInternalInsetsListener( diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java index 65d4e23f7734..870e3bed2b7a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java @@ -132,7 +132,7 @@ public class NavigationModeController implements Dumpable { Settings.Secure.putString(mCurrentUserContext.getContentResolver(), Secure.NAVIGATION_MODE, String.valueOf(mode))); if (DEBUG) { - Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode); + Log.d(TAG, "updateCurrentInteractionMode: mode=" + mode); dumpAssetPaths(mCurrentUserContext); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 292cc7a7deaa..088743cd0f94 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -93,6 +93,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); + private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; + private static final int MAX_NUM_LOGGED_GESTURES = 10; + // Temporary log until b/176302696 is resolved static final boolean DEBUG_MISSING_GESTURE = true; static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; @@ -222,8 +225,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private String mPackageName; private float mMLResults; - private static final int MAX_LOGGED_PREDICTIONS = 10; + // For debugging private ArrayDeque<String> mPredictionLog = new ArrayDeque<>(); + private ArrayDeque<String> mGestureLog = new ArrayDeque<>(); private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -607,7 +611,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } // Check if we are within the tightest bounds beyond which // we would not need to run the ML model. - boolean withinRange = x <= mMLEnableWidth + mLeftInset + boolean withinRange = x < mMLEnableWidth + mLeftInset || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); if (!withinRange) { int results = -1; @@ -617,17 +621,20 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa // Denotes whether we should proceed with the gesture. // Even if it is false, we may want to log it assuming // it is not invalid due to exclusion. - withinRange = x <= mEdgeWidthLeft + mLeftInset + withinRange = x < mEdgeWidthLeft + mLeftInset || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); } } // For debugging purposes - if (mPredictionLog.size() >= MAX_LOGGED_PREDICTIONS) { + if (mPredictionLog.size() >= MAX_NUM_LOGGED_PREDICTIONS) { mPredictionLog.removeFirst(); } - mPredictionLog.addLast(String.format("[%d,%d,%d,%f,%d]", - x, y, app, mMLResults, withinRange ? 1 : 0)); + mPredictionLog.addLast(String.format("Prediction [%d,%d,%d,%d,%f,%d]", + System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, mPredictionLog.peekLast()); + } // Always allow if the user is in a transient sticky immersive state if (mIsNavBarShownTransiently) { @@ -689,6 +696,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private void onMotionEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); + } + // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden mInputEventReceiver.setBatchingEnabled(false); @@ -709,6 +720,19 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mEndPoint.set(-1, -1); mThresholdCrossed = false; } + + // For debugging purposes + if (mGestureLog.size() >= MAX_NUM_LOGGED_GESTURES) { + mGestureLog.removeFirst(); + } + mGestureLog.addLast(String.format( + "Gesture [%d,alw=%B,%B, %B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", + System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, mIsBackGestureAllowed, + QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, + mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, mGestureLog.peekLast()); + } } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { mEndPoint.x = (int) ev.getX(); @@ -827,18 +851,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa public void dump(PrintWriter pw) { pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); + pw.println(" mIsAttached=" + mIsAttached); pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); + pw.println(" mIsGesturalModeEnabled=" + mIsGesturalModeEnabled); + pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); + pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); pw.println(" mAllowGesture=" + mAllowGesture); + pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); - pw.println(" mIsAttached=" + mIsAttached); + pw.println(" mPipExcludedBounds=" + mPipExcludedBounds); pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); - pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); - pw.println(" mPredictionLog=" + String.join(";", mPredictionLog)); + pw.println(" mLeftInset=" + mLeftInset); + pw.println(" mRightInset=" + mRightInset); + pw.println(" mMLEnableWidth=" + mMLEnableWidth); + pw.println(" mMLModelThreshold=" + mMLModelThreshold); + pw.println(" mTouchSlop=" + mTouchSlop); + pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); + pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); + pw.println(" mGestureLog=" + String.join("\n", mGestureLog)); pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 2f9b17aece8e..2ea8657f88e5 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -19,8 +19,6 @@ package com.android.systemui.people; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; -import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle; - import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; @@ -155,7 +153,7 @@ public class PeopleSpaceActivity extends Activity { if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); mLauncherApps.cacheShortcuts(tile.getPackageName(), Collections.singletonList(tile.getId()), - getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); } catch (Exception e) { Log.w(TAG, "Exception caching shortcut:" + e); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java index 9ae7847031aa..6f89332e66a9 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.LauncherApps; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.os.UserHandle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -82,7 +81,7 @@ public class PeopleSpaceTileView extends LinearLayout { public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) { mTileView.setOnClickListener(v -> launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null, - UserHandle.getUserHandleForUid(tile.getUid()))); + tile.getUserHandle())); } /** Sets the click listener of the tile directly. */ diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 5dda23e4a47e..41080afb3604 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -55,7 +55,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; -import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; @@ -160,7 +159,6 @@ public class PeopleSpaceUtils { List<ConversationChannelWrapper> conversations = notificationManager.getConversations( false).getList(); - // Add priority conversations to tiles list. Stream<ShortcutInfo> priorityConversations = conversations.stream() .filter(c -> c.getNotificationChannel() != null @@ -252,7 +250,11 @@ public class PeopleSpaceUtils { } // If tile is null, we need to retrieve from persisted storage. - if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); + if (DEBUG) { + Log.d(TAG, + "Retrieving from storage after reboots: " + shortcutId + " user: " + userId + + " pkg: " + pkg); + } LauncherApps launcherApps = context.getSystemService(LauncherApps.class); ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); if (channel == null) { @@ -384,7 +386,7 @@ public class PeopleSpaceUtils { PeopleSpaceTile tile, Map<String, NotificationEntry> visibleNotifications) { String shortcutId = tile.getId(); String packageName = tile.getPackageName(); - int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); + int userId = getUserId(tile); String key = getKey(shortcutId, packageName, userId); if (!visibleNotifications.containsKey(key)) { if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key); @@ -641,7 +643,8 @@ public class PeopleSpaceUtils { activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, @@ -788,7 +791,6 @@ public class PeopleSpaceUtils { 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"); @@ -1069,11 +1071,6 @@ public class PeopleSpaceUtils { /** Returns the userId associated with a {@link PeopleSpaceTile} */ public static int getUserId(PeopleSpaceTile tile) { - return getUserHandle(tile).getIdentifier(); - } - - /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */ - public static UserHandle getUserHandle(PeopleSpaceTile tile) { - return UserHandle.getUserHandleForUid(tile.getUid()); + return tile.getUserHandle().getIdentifier(); } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java index 358f3113ef88..13e30f920f42 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -41,7 +41,8 @@ public class LaunchConversationActivity extends Activity { Intent intent = getIntent(); String tileId = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID); String packageName = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME); - int uid = intent.getIntExtra(PeopleSpaceWidgetProvider.EXTRA_UID, 0); + UserHandle userHandle = intent.getParcelableExtra( + PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE); if (tileId != null && !tileId.isEmpty()) { if (DEBUG) { @@ -52,7 +53,7 @@ public class LaunchConversationActivity extends Activity { LauncherApps launcherApps = getApplicationContext().getSystemService(LauncherApps.class); launcherApps.startShortcut( - packageName, tileId, null, null, UserHandle.getUserHandleForUid(uid)); + packageName, tileId, null, null, userHandle); } catch (Exception e) { Log.e(TAG, "Exception starting shortcut:" + e); } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 3d1055fdece2..90baf56e0137 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -48,7 +48,7 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { public static final String EXTRA_TILE_ID = "extra_tile_id"; public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; - public static final String EXTRA_UID = "extra_uid"; + public static final String EXTRA_USER_HANDLE = "extra_user_handle"; public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index 80794cb64883..050352292b38 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -123,7 +123,8 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); fillInIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); personView.setOnClickFillInIntent(R.id.item, fillInIntent); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve shortcut information", e); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c8edaec98ee4..fe76668ab68b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -29,7 +29,6 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.tileimpl.QSTileBaseView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -56,7 +55,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final ArrayList<View> mAllViews = new ArrayList<>(); /** * List of {@link View}s representing Quick Settings that are being animated from the quick QS - * position to the normal QS panel. + * position to the normal QS panel. These views will only show once the animation is complete, + * to prevent overlapping of semi transparent views */ private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; @@ -233,7 +233,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Quick tiles. QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle(); getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view); getRelativePosition(loc2, tileIcon, view); @@ -255,11 +254,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - if (mFeatureFlags.isQSLabelsEnabled()) { - firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0); - mAllViews.add(qqsBgCircle); - } - } else { // These tiles disappear when expanding firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); @@ -271,7 +265,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationX); } - mQuickQsViews.add(tileView.getIconWithBackground()); + if (mFeatureFlags.isQSLabelsEnabled()) { + mQuickQsViews.add(tileView); + } else { + mQuickQsViews.add(tileView.getIconWithBackground()); + } mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -362,7 +360,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if(view == parent || view == null) return; // Ignore tile pages as they can have some offset we don't want to take into account in // RTL. - if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) { + if (!isAPage(view)) { loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } @@ -374,6 +372,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha getRelativePositionInt(loc1, (View) view.getParent(), parent); } + // Returns true if the view is a possible page in PagedTileLayout + private boolean isAPage(View view) { + if (view instanceof PagedTileLayout.TilePage) { + return true; + } else if (view instanceof SideLabelTileLayout) { + return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout); + } + return false; + } + public void setPosition(float position) { if (mNeedsAnimatorUpdate) { updateAnimators(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 16e519657a41..2bea72cc0c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.qs; +import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED; + import android.content.ClipData; import android.content.ClipboardManager; import android.content.Intent; @@ -41,6 +43,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; import javax.inject.Inject; +import javax.inject.Named; /** * Controller for {@link QSFooterView}. @@ -63,6 +66,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final View mEdit; private final MultiUserSwitch mMultiUserSwitch; private final PageIndicator mPageIndicator; + private final View mPowerMenuLite; + private final boolean mShowPMLiteButton; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @@ -123,7 +128,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer, QuickQSPanelController quickQSPanelController, - TunerService tunerService, MetricsLogger metricsLogger) { + TunerService tunerService, MetricsLogger metricsLogger, + @Named(PM_LITE_ENABLED) boolean showPMLiteButton) { super(view); mUserManager = userManager; mUserInfoController = userInfoController; @@ -141,10 +147,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mEdit = mView.findViewById(android.R.id.edit); mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch); mPageIndicator = mView.findViewById(R.id.footer_page_indicator); + mPowerMenuLite = mView.findViewById(R.id.pm_lite); + mShowPMLiteButton = showPMLiteButton; } @Override protected void onViewAttached() { + if (mShowPMLiteButton) { + mPowerMenuLite.setVisibility(View.VISIBLE); + } mView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> mView.updateAnimator( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 91ae571d1cfb..05633071be86 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -112,7 +112,7 @@ public class QSPanel extends LinearLayout implements Tunable { private int mMediaTotalBottomMargin; private int mFooterMarginStartHorizontal; private Consumer<Boolean> mMediaVisibilityChangedListener; - private boolean mSideLabels; + protected boolean mSideLabels; public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -201,16 +201,20 @@ public class QSPanel extends LinearLayout implements Tunable { mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages()); } - // Allow the UI to be as big as it want's to, we're in a scroll view - int newHeight = 10000; - int availableHeight = MeasureSpec.getSize(heightMeasureSpec); - int excessHeight = newHeight - availableHeight; - // Measure with EXACTLY. That way, The content will only use excess height and will - // be measured last, after other views and padding is accounted for. This only - // works because our Layouts in here remeasure themselves with the exact content - // height. - heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); - ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + // In landscape, mTileLayout's parent is not the panel but a view that contains the + // tile layout and the media controls. + if (((View) mTileLayout).getParent() == this) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -758,7 +762,7 @@ public class QSPanel extends LinearLayout implements Tunable { // Let's use 3 columns to match the current layout int columns; if (mSideLabels) { - columns = horizontal ? 1 : 2; + columns = horizontal ? 2 : 4; } else { columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index fcb35e2040ea..d60801ef2d03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -18,7 +18,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import android.annotation.NonNull; @@ -66,7 +65,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private BrightnessMirrorController mBrightnessMirrorController; private boolean mGridContentVisible = true; - private boolean mQsLabelsFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -93,7 +91,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSlider.Factory brightnessSliderFactory, - @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); @@ -108,9 +105,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mView.setBrightnessView(mBrightnessSlider.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider); - - mQsLabelsFlag = qsLabelsFlag; - mSideLabels = qsLabelsFlag; } @Override @@ -329,7 +323,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onTuningChanged(String key, String newValue) { if (QS_REMOVE_LABELS.equals(key)) { - if (!mQsLabelsFlag) return; + if (!mQSLabelFlag) return; boolean newShowLabels = newValue == null || "0".equals(newValue); if (mShowLabels == newShowLabels) return; mShowLabels = newShowLabels; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 9426e7122c1c..f1174fbe7aef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -75,7 +75,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; protected boolean mShowLabels = true; - protected boolean mSideLabels; + protected boolean mQSLabelFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -118,11 +118,12 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQSLogger = qsLogger; mDumpManager = dumpManager; mFeatureFlags = featureFlags; + mQSLabelFlag = featureFlags.isQSLabelsEnabled(); } @Override protected void onInit() { - mView.initialize(mSideLabels); + mView.initialize(mQSLabelFlag); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index a29ac3bb77e9..9b66b59c06df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -69,12 +69,22 @@ public class QuickQSPanel extends QSPanel { @Override public TileLayout createRegularTileLayout() { - return new QuickQSPanel.HeaderTileLayout(mContext); + if (mSideLabels) { + return new QQSSideLabelTileLayout(mContext); + } else { + return new QuickQSPanel.HeaderTileLayout(mContext); + } } @Override protected QSTileLayout createHorizontalTileLayout() { - return new DoubleLineTileLayout(mContext); + if (mSideLabels) { + TileLayout t = createRegularTileLayout(); + t.setMaxColumns(2); + return t; + } else { + return new DoubleLineTileLayout(mContext); + } } @Override @@ -331,4 +341,38 @@ public class QuickQSPanel extends QSPanel { } } } + + static class QQSSideLabelTileLayout extends SideLabelTileLayout { + QQSSideLabelTileLayout(Context context) { + super(context, null); + setClipChildren(false); + setClipToPadding(false); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_HORIZONTAL; + setLayoutParams(lp); + setMaxColumns(4); + } + + @Override + public boolean updateResources() { + boolean b = super.updateResources(); + mMaxAllowedRows = 2; + return b; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateResources(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Make sure to always use the correct number of rows. As it's determined by the + // columns, just use as many as needed. + updateMaxRows(10000, mRecords.size()); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 383e932a6955..671f8f7dd2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import com.android.internal.logging.MetricsLogger; @@ -42,8 +41,6 @@ import javax.inject.Named; @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { - private boolean mUseSideLabels; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = newConfig -> { int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); @@ -58,12 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, - FeatureFlags featureFlags + DumpManager dumpManager, FeatureFlags featureFlags ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); - mUseSideLabels = qsLabelsFlag; } @Override @@ -104,19 +99,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> break; } } - if (mUseSideLabels) { - List<QSTile> newTiles = new ArrayList<>(); - for (int i = 0; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - for (int i = 1; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - super.setTiles(newTiles, true); - - } else { - super.setTiles(tiles, true); - } + super.setTiles(tiles, !mQSLabelFlag); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 74a7ac1cb6dd..4de4a78e9e8a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -20,11 +20,13 @@ import android.content.Context import android.util.AttributeSet import com.android.systemui.R -open class SideLabelTileLayout(context: Context, attrs: AttributeSet) : TileLayout(context, attrs) { +open class SideLabelTileLayout( + context: Context, + attrs: AttributeSet? +) : TileLayout(context, attrs) { override fun updateResources(): Boolean { return super.updateResources().also { - mResourceColumns = 2 mMaxAllowedRows = 4 mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() mCellMarginVertical = mCellMarginHorizontal diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index d559e07f3ff6..14cbf980df23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -124,6 +124,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + updateColumns(); mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 048fdc3a0e5a..7a91421b00a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -75,6 +75,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private final Context mContext; private final Handler mHandler = new Handler(); @@ -87,6 +89,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private int mEditIndex; private int mTileDividerIndex; private int mFocusIndex; + private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -109,7 +112,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mDecoration = new TileItemDecoration(context); mMarginDecoration = new MarginTileDecoration(); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); - mNumColumns = context.getResources().getInteger(R.integer.quick_settings_num_columns); + mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); } @@ -129,7 +132,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta * @return {@code true} if the number of columns changed, {@code false} otherwise */ public boolean updateNumColumns() { - int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns); + int numColumns = mContext.getResources().getInteger(NUM_COLUMNS_ID); if (numColumns != mNumColumns) { mNumColumns = numColumns; return true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java index 35a8257bd5a7..10192bc20df9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java @@ -21,6 +21,7 @@ import android.hardware.display.ColorDisplayManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.util.settings.GlobalSettings; import javax.inject.Named; @@ -31,6 +32,8 @@ import dagger.Provides; public interface QSFlagsModule { String QS_LABELS_FLAG = "qs_labels_flag"; String RBC_AVAILABLE = "rbc_available"; + String PM_LITE_ENABLED = "pm_lite"; + String PM_LITE_SETTING = "sysui_pm_lite"; @Provides @SysUISingleton @@ -46,4 +49,11 @@ public interface QSFlagsModule { static boolean isReduceBrightColorsAvailable(Context context) { return ColorDisplayManager.isReduceBrightColorsAvailable(context); } + + @Provides + @SysUISingleton + @Named(PM_LITE_ENABLED) + static boolean isPMLiteEnabled(FeatureFlags featureFlags, GlobalSettings globalSettings) { + return featureFlags.isPMLiteEnabled() && globalSettings.getInt(PM_LITE_SETTING, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index a699e2ec7cfc..424aafac1f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -105,22 +105,25 @@ public class QSTileView extends QSTileBaseView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mLabel.setSingleLine(false); super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Remeasure view if the primary label requires more then 2 lines or the secondary label - // text will be cut off. - if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText()) - && mSecondLine.getLineHeight() > mSecondLine.getHeight()) { - if (!mLabel.isSingleLine()) { - mLabel.setSingleLine(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } else { - if (mLabel.isSingleLine()) { - mLabel.setSingleLine(false); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + // Remeasure view if the primary label requires more than mMaxLabelLines lines or the + // secondary label text will be cut off. + if (shouldLabelBeSingleLine()) { + mLabel.setSingleLine(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private boolean shouldLabelBeSingleLine() { + if (mLabel.getLineCount() > mMaxLabelLines) { + return true; + } else if (!TextUtils.isEmpty(mSecondLine.getText()) + && mLabel.getLineCount() > mMaxLabelLines - 1) { + return true; } + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index dc81b702021f..07d48f32ff20 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tileimpl +import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList import android.graphics.Color @@ -24,20 +25,24 @@ import android.graphics.drawable.PaintDrawable import android.graphics.drawable.RippleDrawable import android.service.quicksettings.Tile.STATE_ACTIVE import android.view.Gravity -import android.view.View import android.widget.LinearLayout +import android.widget.RelativeLayout import com.android.systemui.R import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState +// Placeholder +private const val CORNER_RADIUS = 40f + class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { private var paintDrawable: PaintDrawable? = null - private var divider: View? = null + private var paintColor = Color.TRANSPARENT + private var paintAnimator: ValueAnimator? = null init { orientation = HORIZONTAL @@ -49,7 +54,12 @@ class QSTileViewHorizontal( override fun createLabel() { super.createLabel() - findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START + findViewById<LinearLayout>(R.id.label_group)?.apply { + gravity = Gravity.START + (layoutParams as? RelativeLayout.LayoutParams)?.apply { + removeRule(RelativeLayout.ALIGN_PARENT_TOP) + } + } mLabel.gravity = Gravity.START mLabel.textDirection = TEXT_DIRECTION_LOCALE mSecondLine.gravity = Gravity.START @@ -57,7 +67,7 @@ class QSTileViewHorizontal( val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) mLabelContainer.setPaddingRelative(0, padding, padding, padding) (mLabelContainer.layoutParams as LayoutParams).gravity = - Gravity.CENTER_VERTICAL or Gravity.START + Gravity.CENTER_VERTICAL or Gravity.START } override fun updateRippleSize() { @@ -66,8 +76,8 @@ class QSTileViewHorizontal( override fun newTileBackground(): Drawable? { val d = super.newTileBackground() if (paintDrawable == null) { - paintDrawable = PaintDrawable(Color.WHITE).apply { - setCornerRadius(50f) + paintDrawable = PaintDrawable(paintColor).apply { + setCornerRadius(CORNER_RADIUS) } } if (d is RippleDrawable) { @@ -90,10 +100,39 @@ class QSTileViewHorizontal( override fun handleStateChanged(state: QSTile.State) { super.handleStateChanged(state) - paintDrawable?.setTint(getCircleColor(state.state)) mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - divider?.backgroundTintList = mLabel.textColors + + val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT + val newColor = getCircleColor(state.state) + if (allowAnimations) { + animateToNewState(newColor) + } else { + if (newColor != paintColor) { + clearAnimator() + paintDrawable?.paint?.color = newColor + paintDrawable?.invalidateSelf() + } + } + paintColor = newColor + } + + private fun animateToNewState(newColor: Int) { + if (newColor != paintColor) { + clearAnimator() + paintAnimator = ValueAnimator.ofArgb(paintColor, newColor) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { animation: ValueAnimator -> + paintDrawable?.paint?.color = animation.animatedValue as Int + paintDrawable?.invalidateSelf() + } + start() + } + } + } + + private fun clearAnimator() { + paintAnimator?.cancel()?.also { paintAnimator = null } } override fun handleExpand(dualTarget: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 3841daca7ebe..70287cd37d01 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; +import static android.content.pm.PackageManager.FEATURE_CAMERA_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,8 +61,8 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return /*getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) - && */whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", false)); } @@ -75,7 +78,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return CAMERA; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2f0071a1f198..e9b712df2154 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; +import static android.content.pm.PackageManager.FEATURE_MICROPHONE_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,7 +61,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager() + .hasSystemFeature(FEATURE_MICROPHONE_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", false)); } @@ -74,7 +79,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return MICROPHONE; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 00703e7f8403..0c582bdbe12f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles; import android.content.Intent; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; @@ -49,7 +49,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS /** * @return Id of the sensor that will be toggled */ - public abstract @IndividualSensor int getSensorId(); + public abstract @Sensor int getSensorId(); /** * @return icon for the QS tile diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 1386ddfa7692..53d9f1c08e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -97,8 +97,8 @@ public class CropView extends View { float bottom = mBottomCrop + mBottomDelta; drawShade(canvas, 0, top); drawShade(canvas, bottom, 1f); - drawHandle(canvas, top); - drawHandle(canvas, bottom); + drawHandle(canvas, top, /* draw the handle tab down */ false); + drawHandle(canvas, bottom, /* draw the handle tab up */ true); } @Override @@ -122,7 +122,7 @@ public class CropView extends View { } else { // Bottom mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta, topPx + 2 * mCropTouchMargin - bottomPx, - getMeasuredHeight() - bottomPx)); + getHeight() - bottomPx)); } updateListener(event); invalidate(); @@ -212,21 +212,25 @@ public class CropView extends View { } private void drawShade(Canvas canvas, float fracStart, float fracEnd) { - canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(), + canvas.drawRect(0, fractionToPixels(fracStart), getWidth(), fractionToPixels(fracEnd), mShadePaint); } - private void drawHandle(Canvas canvas, float frac) { + private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) { int y = fractionToPixels(frac); - canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint); + canvas.drawLine(0, y, getWidth(), y, mHandlePaint); + float radius = 15 * getResources().getDisplayMetrics().density; + float x = getWidth() * .9f; + canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180, + true, mHandlePaint); } private int fractionToPixels(float frac) { - return (int) (frac * getMeasuredHeight()); + return (int) (frac * getHeight()); } private float pixelsToFraction(int px) { - return px / (float) getMeasuredHeight(); + return px / (float) getHeight(); } private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index a6433ae94b2b..89efda98a5b6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -46,6 +46,7 @@ public class LongScreenshotActivity extends Activity { private final UiEventLogger mUiEventLogger; private final ScrollCaptureController mScrollCaptureController; + private final ScrollCaptureClient.Connection mConnection; private ImageView mPreview; private View mSave; @@ -69,8 +70,10 @@ public class LongScreenshotActivity extends Activity { Context context) { mUiEventLogger = uiEventLogger; - mScrollCaptureController = new ScrollCaptureController(context, - ScreenshotController.sScrollConnection, mainExecutor, bgExecutor, imageExporter); + mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor, + imageExporter); + + mConnection = ScreenshotController.takeScrollCaptureConnection(); } @Override @@ -98,15 +101,20 @@ public class LongScreenshotActivity extends Activity { public void onStart() { super.onStart(); if (mPreview.getDrawable() == null) { + if (mConnection == null) { + Log.e(TAG, "Failed to get scroll capture connection, bailing out"); + finishAndRemoveTask(); + return; + } doCapture(); } } - private void disableButtons() { - mSave.setEnabled(false); - mCancel.setEnabled(false); - mEdit.setEnabled(false); - mShare.setEnabled(false); + private void setButtonsEnabled(boolean enabled) { + mSave.setEnabled(enabled); + mCancel.setEnabled(enabled); + mEdit.setEnabled(enabled); + mShare.setEnabled(enabled); } private void doEdit(Uri uri) { @@ -115,8 +123,7 @@ public class LongScreenshotActivity extends Activity { if (!TextUtils.isEmpty(editorPackage)) { intent.setComponent(ComponentName.unflattenFromString(editorPackage)); } - intent.setType("image/png"); - intent.setData(uri); + intent.setDataAndType(uri, "image/png"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -127,12 +134,11 @@ public class LongScreenshotActivity extends Activity { private void doShare(Uri uri) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/png"); - intent.setData(uri); + intent.putExtra(Intent.EXTRA_STREAM, uri); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); Intent sharingChooserIntent = Intent.createChooser(intent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION); + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); } @@ -140,7 +146,7 @@ public class LongScreenshotActivity extends Activity { private void onClicked(View v) { int id = v.getId(); v.setPressed(true); - disableButtons(); + setButtonsEnabled(false); if (id == R.id.save) { startExport(PendingAction.SAVE); } else if (id == R.id.cancel) { @@ -160,10 +166,12 @@ public class LongScreenshotActivity extends Activity { @Override public void onError() { Log.e(TAG, "Error exporting image data."); + setButtonsEnabled(true); } @Override public void onExportComplete(Uri outputUri) { + setButtonsEnabled(true); switch (action) { case EDIT: doEdit(outputUri); @@ -181,7 +189,8 @@ public class LongScreenshotActivity extends Activity { } private void doCapture() { - mScrollCaptureController.start(new ScrollCaptureController.ScrollCaptureCallback() { + mScrollCaptureController.start(mConnection, + new ScrollCaptureController.ScrollCaptureCallback() { @Override public void onError() { Log.e(TAG, "Error!"); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS new file mode 100644 index 000000000000..9b3e386bc0d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS @@ -0,0 +1,12 @@ +# Scroll Capture (Long Screenshots) +# Bug component: 801322 +# +# Referenced by: +# +# core/java/src/android/view/OWNERS +# core/java/src/com/android/internal/view/OWNERS +# core/tests/coretests/src/android/view/OWNERS +# core/tests/coretests/src/com/android/internal/view/OWNERS + +mrcasey@google.com +mrenouf@google.com diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 31c693bdde1f..805ac7cf1ec9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -101,7 +101,7 @@ import javax.inject.Inject; public class ScreenshotController { private static final String TAG = logTag(ScreenshotController.class); - public static ScrollCaptureClient.Connection sScrollConnection; + private static ScrollCaptureClient.Connection sScrollConnection; /** * POD used in the AsyncTask which saves an image in the background. @@ -222,6 +222,12 @@ public class ScreenshotController { | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); + public static @Nullable ScrollCaptureClient.Connection takeScrollCaptureConnection() { + ScrollCaptureClient.Connection connection = sScrollConnection; + sScrollConnection = null; + return connection; + } + @Inject ScreenshotController( Context context, @@ -319,6 +325,7 @@ public class ScreenshotController { attachWindow(); mWindow.setContentView(mScreenshotView); + mScreenshotView.requestApplyInsets(); mScreenshotView.takePartialScreenshot( rect -> takeScreenshotInternal(finisher, rect)); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index dc639dce4951..54b99bbe74cc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -21,24 +21,25 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; import static java.lang.Math.min; import static java.util.Objects.requireNonNull; +import android.annotation.BinderThread; import android.annotation.UiContext; import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.media.Image; import android.media.ImageReader; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; import android.view.IScrollCaptureCallbacks; import android.view.IScrollCaptureConnection; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.view.ScrollCaptureViewSupport; import java.util.function.Consumer; @@ -62,16 +63,19 @@ public class ScrollCaptureClient { */ public interface Connection { /** - * Session start should be deferred until UI is active because of resource allocation and - * potential visible side effects in the target window. - * + * Start a session. + * @param sessionConsumer listener to receive the session once active * @param maxPages the capture buffer size expressed as a multiple of the content height */ + // TODO ListenableFuture void start(Consumer<Session> sessionConsumer, float maxPages); /** - * Close the connection. + * Close the connection. Must end capture if started to avoid potential unwanted visual + * artifacts. + * + * @see Session#end(Runnable) */ void close(); } @@ -119,6 +123,7 @@ public class ScrollCaptureClient { * @param top the top (y) position of the tile to capture, in content rect space * @param consumer listener to be informed of the result */ + // TODO ListenableFuture void requestTile(int top, Consumer<CaptureResult> consumer); /** @@ -129,16 +134,31 @@ public class ScrollCaptureClient { */ int getMaxTiles(); + /** + * @return the height of each image tile + */ int getTileHeight(); + /** + * @return the height of scrollable content being captured + */ int getPageHeight(); + /** + * @return the width of the scrollable page + */ int getPageWidth(); /** + * @return the bounds on screen of the window being captured. + */ + Rect getWindowBounds(); + + /** * End the capture session, return the target app to original state. The listener * will be called when the target app is ready to before visible and interactive. */ + // TODO ListenableFuture void end(Runnable listener); } @@ -185,13 +205,13 @@ public class ScrollCaptureClient { + ", taskId=" + taskId + ", consumer=" + consumer + ")"); } mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId, - new ControllerCallbacks(consumer)); + new ClientCallbacks(consumer)); } catch (RemoteException e) { Log.e(TAG, "Ignored remote exception", e); } } - private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements + private static class ClientCallbacks extends IScrollCaptureCallbacks.Stub implements Connection, Session, IBinder.DeathRecipient { private IScrollCaptureConnection mConnection; @@ -206,46 +226,63 @@ public class ScrollCaptureClient { private int mTileWidth; private Rect mRequestRect; private boolean mStarted; + + private ICancellationSignal mCancellationSignal; + private Rect mWindowBounds; + private Rect mBoundsInWindow; private int mMaxTiles; - private ControllerCallbacks(Consumer<Connection> connectionConsumer) { + private ClientCallbacks(Consumer<Connection> connectionConsumer) { mConnectionConsumer = connectionConsumer; } - // IScrollCaptureCallbacks - + @BinderThread @Override - public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds, - Point positionInWindow) throws RemoteException { + public void onScrollCaptureResponse(ScrollCaptureResponse response) throws RemoteException { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds - + ", positionInWindow=" + positionInWindow + ")"); + Log.d(TAG, "onScrollCaptureResponse(response=" + response + ")"); } - mConnection = connection; - mConnection.asBinder().linkToDeath(this, 0); - mScrollBounds = scrollBounds; - mConnectionConsumer.accept(this); - mConnectionConsumer = null; - - int pxPerPage = mScrollBounds.width() * mScrollBounds.height(); - int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); - mTileWidth = mScrollBounds.width(); - mTileHeight = pxPerTile / mScrollBounds.width(); - if (DEBUG_SCROLL) { - Log.d(TAG, "scrollBounds: " + mScrollBounds); - Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight); + if (response.isConnected()) { + mConnection = response.getConnection(); + mConnection.asBinder().linkToDeath(this, 0); + mWindowBounds = response.getWindowBounds(); + mBoundsInWindow = response.getBoundsInWindow(); + + int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height(); + int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); + mTileWidth = mBoundsInWindow.width(); + mTileHeight = pxPerTile / mBoundsInWindow.width(); + if (DEBUG_SCROLL) { + Log.d(TAG, "boundsInWindow: " + mBoundsInWindow); + Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight); + Log.d(TAG, "maxHeight: " + (mMaxTiles * mTileHeight) + "px"); + } + mConnectionConsumer.accept(this); } + mConnectionConsumer = null; } @Override - public void onUnavailable() throws RemoteException { + public void start(Consumer<Session> sessionConsumer, float maxPages) { if (DEBUG_SCROLL) { - Log.d(TAG, "onUnavailable"); + Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," + + " maxPages=" + maxPages + ")"); + } + mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); + mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, + mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + mSessionConsumer = sessionConsumer; + + try { + mCancellationSignal = mConnection.startCapture(mReader.getSurface()); + mStarted = true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to start", e); + mReader.close(); } - // The targeted app does not support scroll capture - // or the window could not be found... etc etc. } + @BinderThread @Override public void onCaptureStarted() { if (DEBUG_SCROLL) { @@ -256,13 +293,25 @@ public class ScrollCaptureClient { } @Override - public void onCaptureBufferSent(long frameNumber, Rect contentArea) { - Image image = null; - if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) { - image = mReader.acquireNextImage(); + public void requestTile(int top, Consumer<CaptureResult> consumer) { + if (DEBUG_SCROLL) { + Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); + } + cancelPendingRequest(); + mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); + mResultConsumer = consumer; + try { + mCancellationSignal = mConnection.requestImage(mRequestRect); + } catch (RemoteException e) { + Log.e(TAG, "Caught remote exception from requestImage", e); } + } + + @Override + public void onImageRequestCompleted(int flags, Rect contentArea) { + Image image = mReader.acquireLatestImage(); if (DEBUG_SCROLL) { - Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber + Log.d(TAG, "onCaptureBufferSent(flags=" + flags + ", contentArea=" + contentArea + ") image=" + image); } // Save and clear first, since the consumer will likely request the next @@ -273,17 +322,49 @@ public class ScrollCaptureClient { } @Override - public void onConnectionClosed() { + public void end(Runnable listener) { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnectionClosed()"); + Log.d(TAG, "end(listener=" + listener + ")"); } - disconnect(); + if (mStarted) { + mShutdownListener = listener; + mReader.close(); + try { + // listener called from onConnectionClosed callback + mConnection.endCapture(); + } catch (RemoteException e) { + Log.d(TAG, "Ignored exception from endCapture()", e); + disconnect(); + listener.run(); + } + } else { + disconnect(); + listener.run(); + } + } + + @BinderThread + @Override + public void onCaptureEnded() { + close(); if (mShutdownListener != null) { mShutdownListener.run(); mShutdownListener = null; } } + @Override + public void close() { + if (mConnection != null) { + try { + mConnection.close(); + } catch (RemoteException e) { + /* ignore */ + } + disconnect(); + } + } + // Misc private void disconnect() { @@ -293,63 +374,25 @@ public class ScrollCaptureClient { mConnection = null; } - // ScrollCaptureController.Connection - - @Override - public void start(Consumer<Session> sessionConsumer, float maxPages) { - if (DEBUG_SCROLL) { - Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," - + " maxPages=" + maxPages + ")" - + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]"); - } - mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); - mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, - mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); - mSessionConsumer = sessionConsumer; - try { - mConnection.startCapture(mReader.getSurface()); - mStarted = true; - } catch (RemoteException e) { - Log.w(TAG, "Failed to start", e); - } - } - - @Override - public void close() { - end(null); - } - - // ScrollCaptureController.Session - + /** + * The process hosting the window went away abruptly! + */ @Override - public void end(Runnable listener) { + public void binderDied() { if (DEBUG_SCROLL) { - Log.d(TAG, "end(listener=" + listener + ")"); - } - if (mStarted) { - mShutdownListener = listener; - try { - // listener called from onConnectionClosed callback - mConnection.endCapture(); - } catch (RemoteException e) { - Log.d(TAG, "Ignored exception from endCapture()", e); - disconnect(); - listener.run(); - } - } else { - disconnect(); - listener.run(); + Log.d(TAG, "binderDied()"); } + disconnect(); } @Override public int getPageHeight() { - return mScrollBounds.height(); + return mBoundsInWindow.height(); } @Override public int getPageWidth() { - return mScrollBounds.width(); + return mBoundsInWindow.width(); } @Override @@ -357,34 +400,24 @@ public class ScrollCaptureClient { return mTileHeight; } - @Override - public int getMaxTiles() { - return mMaxTiles; + public Rect getWindowBounds() { + return new Rect(mWindowBounds); } @Override - public void requestTile(int top, Consumer<CaptureResult> consumer) { - if (DEBUG_SCROLL) { - Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); - } - mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); - mResultConsumer = consumer; - try { - mConnection.requestImage(mRequestRect); - } catch (RemoteException e) { - Log.e(TAG, "Caught remote exception from requestImage", e); - } + public int getMaxTiles() { + return mMaxTiles; } - /** - * The process hosting the window went away abruptly! - */ - @Override - public void binderDied() { - if (DEBUG_SCROLL) { - Log.d(TAG, "binderDied()"); + private void cancelPendingRequest() { + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException e) { + /* ignore */ + } + mCancellationSignal = null; } - disconnect(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 863116a22ee4..4a3ffa45ab81 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -53,7 +53,6 @@ public class ScrollCaptureController { public static final int MAX_HEIGHT = 12000; - private final Connection mConnection; private final Context mContext; private final Executor mUiExecutor; @@ -65,10 +64,9 @@ public class ScrollCaptureController { private UUID mRequestId; private ScrollCaptureCallback mCaptureCallback; - public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor, - Executor bgExecutor, ImageExporter exporter) { + public ScrollCaptureController(Context context, Executor uiExecutor, Executor bgExecutor, + ImageExporter exporter) { mContext = context; - mConnection = connection; mUiExecutor = uiExecutor; mBgExecutor = bgExecutor; mImageExporter = exporter; @@ -78,16 +76,17 @@ public class ScrollCaptureController { /** * Run scroll capture! * + * @param connection connection to the remote window to be used * @param callback request callback to report back to the service */ - public void start(ScrollCaptureCallback callback) { + public void start(Connection connection, ScrollCaptureCallback callback) { mCaptureTime = ZonedDateTime.now(); mRequestId = UUID.randomUUID(); mCaptureCallback = callback; float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(), SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT); - mConnection.start(this::startCapture, maxPages); + connection.start(this::startCapture, maxPages); } /** diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 9f182e19efaf..658613796498 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -25,8 +25,8 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_SENSOR -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE import android.os.Bundle import android.os.Handler import android.text.Html @@ -81,7 +81,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene dismiss() } } - if (!sensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)) { + if (!sensorPrivacyManager.isSensorPrivacyEnabled(sensor)) { finish() return } @@ -89,9 +89,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene mAlertParams.apply { try { mMessage = Html.fromHtml(getString(when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> R.string.sensor_privacy_start_use_mic_dialog_content - INDIVIDUAL_SENSOR_CAMERA -> + CAMERA -> R.string.sensor_privacy_start_use_camera_dialog_content else -> Resources.ID_NULL }, packageManager.getApplicationInfo(sensorUsePackageName, 0) @@ -102,9 +102,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } mIconId = when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> com.android.internal.R.drawable.perm_group_microphone - INDIVIDUAL_SENSOR_CAMERA -> com.android.internal.R.drawable.perm_group_camera + CAMERA -> com.android.internal.R.drawable.perm_group_camera else -> Resources.ID_NULL } @@ -121,7 +121,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene override fun onStart() { super.onStart() - sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true) + sensorPrivacyManager.suppressSensorPrivacyReminders(sensorUsePackageName, true) unsuppressImmediately = false } @@ -156,11 +156,11 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene if (unsuppressImmediately) { sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) } else { Handler(mainLooper).postDelayed({ sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) } } @@ -170,7 +170,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } private fun disableSensorPrivacy() { - sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false) + sensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, false) unsuppressImmediately = true setResult(RESULT_OK) } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 0bfc8e5d554b..fea521f15b84 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -72,8 +72,6 @@ public class BrightnessController implements ToggleSlider.Listener { private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT); - private final float mMinimumBacklight; - private final float mMaximumBacklight; private final float mDefaultBacklight; private final float mMinimumBacklightForVr; private final float mMaximumBacklightForVr; @@ -314,10 +312,6 @@ public class BrightnessController implements ToggleSlider.Listener { mDisplayId = mContext.getDisplayId(); PowerManager pm = context.getSystemService(PowerManager.class); - mMinimumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - mMaximumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); mDefaultBacklight = mContext.getDisplay().getBrightnessDefault(); mMinimumBacklightForVr = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR); @@ -375,8 +369,8 @@ public class BrightnessController implements ToggleSlider.Listener { metric = mAutomatic ? MetricsEvent.ACTION_BRIGHTNESS_AUTO : MetricsEvent.ACTION_BRIGHTNESS; - minBacklight = mMinimumBacklight; - maxBacklight = mMaximumBacklight; + minBacklight = PowerManager.BRIGHTNESS_MIN; + maxBacklight = PowerManager.BRIGHTNESS_MAX; settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT; } final float valFloat = MathUtils.min(convertGammaToLinearFloat(value, @@ -439,8 +433,8 @@ public class BrightnessController implements ToggleSlider.Listener { min = mMinimumBacklightForVr; max = mMaximumBacklightForVr; } else { - min = mMinimumBacklight; - max = mMaximumBacklight; + min = PowerManager.BRIGHTNESS_MIN; + max = PowerManager.BRIGHTNESS_MAX; } // convertGammaToLinearFloat returns 0-1 if (BrightnessSynchronizer.floatEquals(brightnessValue, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 862c27907e0f..1d59257c9c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -82,4 +82,12 @@ public class FeatureFlags { public boolean isMonetEnabled() { return mFlagReader.isEnabled(R.bool.flag_monet); } + + public boolean isNavigationBarOverlayEnabled() { + return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay); + } + + public boolean isPMLiteEnabled() { + return mFlagReader.isEnabled(R.bool.flag_pm_lite); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java index f2adaf042b2f..9ed9659c7ab8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/GestureRecorder.java @@ -207,7 +207,7 @@ public class GestureRecorder { sb.append(g.toJson()); count++; } - mLastSaveLen = count; + mLastSaveLen += count; sb.append("]"); return sb.toString(); } @@ -249,7 +249,9 @@ public class GestureRecorder { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { save(); if (mLastSaveLen >= 0) { - pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile); + pw.println(String.valueOf(mLastSaveLen) + + " gestures since last dump written to " + mLogfile); + mLastSaveLen = 0; } else { pw.println("error writing gestures"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 7c3b791aed09..c8c0755344a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -548,8 +548,6 @@ public class NotificationEntryManager implements try { mStatusBarService.onNotificationClear( notification.getPackageName(), - notification.getTag(), - notification.getId(), notification.getUser().getIdentifier(), notification.getKey(), dismissedByUserStats.dismissalSurface, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index d617dff372da..6b96ee4fc8e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -254,8 +254,6 @@ public class NotifCollection implements Dumpable { try { mStatusBarService.onNotificationClear( entry.getSbn().getPackageName(), - entry.getSbn().getTag(), - entry.getSbn().getId(), entry.getSbn().getUser().getIdentifier(), entry.getSbn().getKey(), stats.dismissalSurface, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index 414d62092ab2..222735aeb35a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -49,7 +49,7 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mView); // Let's invert the notification colors when we're in night mode and // the notification background isn't colorized. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java index 301c3726793a..d21ae13a1e01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java @@ -64,7 +64,7 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mWrappedView); if (needsInversion(resolveBackgroundColor(), mWrappedView)) { invertViewLuminosity(mWrappedView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 5fff8c83048f..89babf0835c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -36,12 +36,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.settingslib.Utils; +import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.TransformState; @@ -58,9 +58,11 @@ public abstract class NotificationViewWrapper implements TransformableView { private final Rect mTmpRect = new Rect(); protected int mBackgroundColor = 0; - private int mLightTextColor; - private int mDarkTextColor; - private int mDefaultTextColor; + private int mMaterialTextColorPrimary; + private int mMaterialTextColorSecondary; + private int mThemedTextColorPrimary; + private int mThemedTextColorSecondary; + private boolean mAdjustTheme; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { @@ -97,6 +99,8 @@ public abstract class NotificationViewWrapper implements TransformableView { mView = view; mRow = row; onReinflated(); + mAdjustTheme = ctx.getResources().getBoolean( + R.bool.config_adjustThemeOnNotificationCustomViews); } /** @@ -121,15 +125,22 @@ public abstract class NotificationViewWrapper implements TransformableView { mBackgroundColor = backgroundColor; mView.setBackground(new ColorDrawable(Color.TRANSPARENT)); } - mLightTextColor = mView.getContext().getColor( - com.android.internal.R.color.notification_primary_text_color_light); - mDarkTextColor = mView.getContext().getColor( - R.color.notification_primary_text_color_dark); + + Context materialTitleContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification_Title); + mMaterialTextColorPrimary = Utils.getColorAttr(materialTitleContext, + com.android.internal.R.attr.textColor).getDefaultColor(); + Context materialContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification); + mMaterialTextColorSecondary = Utils.getColorAttr(materialContext, + com.android.internal.R.attr.textColor).getDefaultColor(); Context themedContext = new ContextThemeWrapper(mView.getContext(), - R.style.Theme_DeviceDefault_DayNight); - mDefaultTextColor = Utils.getColorAttr(themedContext, R.attr.textColorPrimary) - .getDefaultColor(); + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + mThemedTextColorPrimary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorPrimary).getDefaultColor(); + mThemedTextColorSecondary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorSecondary).getDefaultColor(); } protected boolean needsInversion(int defaultBackgroundColor, View view) { @@ -207,38 +218,35 @@ public abstract class NotificationViewWrapper implements TransformableView { return false; } - protected void ensureThemeOnChildren() { - if (mView == null) { + protected void ensureThemeOnChildren(View rootView) { + if (!mAdjustTheme || mView == null || rootView == null) { return; } // Notifications with custom backgrounds should not be adjusted if (mBackgroundColor != Color.TRANSPARENT - || getBackgroundColor(mView) != Color.TRANSPARENT) { + || getBackgroundColor(mView) != Color.TRANSPARENT + || getBackgroundColor(rootView) != Color.TRANSPARENT) { return; } // Now let's check if there's unprotected text somewhere, and apply the theme if we find it. - if (!(mView instanceof ViewGroup)) { - return; - } - processChildrenTextColor((ViewGroup) mView); + processTextColorRecursive(rootView); } - private void processChildrenTextColor(ViewGroup viewGroup) { - if (viewGroup == null) { - return; - } - - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - if (child instanceof TextView) { - int foreground = ((TextView) child).getCurrentTextColor(); - if (foreground == mLightTextColor || foreground == mDarkTextColor) { - ((TextView) child).setTextColor(mDefaultTextColor); - } - } else if (child instanceof ViewGroup) { - processChildrenTextColor((ViewGroup) child); + private void processTextColorRecursive(View view) { + if (view instanceof TextView) { + TextView textView = (TextView) view; + int foreground = textView.getCurrentTextColor(); + if (foreground == mMaterialTextColorPrimary) { + textView.setTextColor(mThemedTextColorPrimary); + } else if (foreground == mMaterialTextColorSecondary) { + textView.setTextColor(mThemedTextColorSecondary); + } + } else if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + processTextColorRecursive(viewGroup.getChildAt(i)); } } } 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 2ce403764c7d..d6380199e844 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -89,7 +89,8 @@ public class KeyguardClockPositionAlgorithm { private int mNotificationStackHeight; /** - * Minimum top margin to avoid overlap with status bar. + * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher + * avatar. */ private int mMinTopMargin; @@ -186,15 +187,15 @@ public class KeyguardClockPositionAlgorithm { /** * Sets up algorithm values. */ - public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight, - float panelExpansion, int parentHeight, int keyguardStatusHeight, - int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY, - boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, - boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon, - float qsExpansion, int cutoutTopInset) { - mMinTopMargin = statusBarMinHeight + (showLockIcon - ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon) - + userSwitchHeight; + public void setup(int keyguardStatusBarHeaderHeight, int maxShadeBottom, + int notificationStackHeight, float panelExpansion, int parentHeight, + int keyguardStatusHeight, int userSwitchHeight, int clockPreferredY, + int userSwitchPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, + float emptyDragAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, + boolean showLockIcon, float qsExpansion, int cutoutTopInset) { + mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(showLockIcon + ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon, + userSwitchHeight); mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; mPanelExpansion = panelExpansion; 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 6940050f754b..83c347b05012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -287,21 +287,6 @@ public class NotificationPanelViewController extends PanelViewController { } }; - final KeyguardUserSwitcherController.KeyguardUserSwitcherListener - mKeyguardUserSwitcherListener = - new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() { - @Override - public void onKeyguardUserSwitcherChanged(boolean open) { - if (mKeyguardUserSwitcherController == null) { - updateUserSwitcherVisibility(false); - } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { - updateUserSwitcherVisibility(open - && mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardFadingAway()); - } - } - }; - private final LayoutInflater mLayoutInflater; private final PowerManager mPowerManager; private final AccessibilityManager mAccessibilityManager; @@ -329,7 +314,6 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; - private boolean mKeyguardUserSwitcherIsShowing; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; @@ -374,6 +358,7 @@ public class NotificationPanelViewController extends PanelViewController { private ValueAnimator mQsExpansionAnimator; private FlingAnimationUtils mFlingAnimationUtils; private int mStatusBarMinHeight; + private int mStatusBarHeaderHeightKeyguard; private int mNotificationsHeaderCollideDistance; private float mEmptyDragAmount; private float mDownX; @@ -620,6 +605,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardQsUserSwitchEnabled = mKeyguardUserSwitcherEnabled && mResources.getBoolean( R.bool.config_keyguard_user_switch_opens_qs_details); + keyguardUpdateMonitor.setKeyguardQsUserSwitchEnabled(mKeyguardQsUserSwitchEnabled); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -772,6 +758,8 @@ public class NotificationPanelViewController extends PanelViewController { .setMaxLengthSeconds(0.4f).build(); mStatusBarMinHeight = mResources.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); + mStatusBarHeaderHeightKeyguard = mResources.getDimensionPixelSize( + R.dimen.status_bar_header_height_keyguard); mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height); mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize( R.dimen.header_notifications_collide_distance); @@ -808,7 +796,6 @@ public class NotificationPanelViewController extends PanelViewController { // Try to close the switcher so that callbacks are triggered if necessary. // Otherwise, NPV can get into a state where some of the views are still hidden mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false); - mKeyguardUserSwitcherController.removeCallback(); } mKeyguardQsUserSwitchController = null; @@ -828,7 +815,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); mKeyguardUserSwitcherController = userSwitcherComponent.getKeyguardUserSwitcherController(); - mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener); mKeyguardUserSwitcherController.init(); mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); } else { @@ -1069,7 +1055,7 @@ public class NotificationPanelViewController extends PanelViewController { int totalHeight = mView.getHeight(); int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); - int userSwitcherPreferredY = mStatusBarMinHeight; + int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard; boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); @@ -1078,7 +1064,8 @@ public class NotificationPanelViewController extends PanelViewController { ? mKeyguardQsUserSwitchController.getUserIconHeight() : (mKeyguardUserSwitcherController != null ? mKeyguardUserSwitcherController.getUserIconHeight() : 0); - mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding, + mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), getExpandedFraction(), totalHeight, @@ -3523,34 +3510,6 @@ public class NotificationPanelViewController extends PanelViewController { return false; } - private void updateUserSwitcherVisibility(boolean open) { - // Do not update if previously called with the same state. - if (mKeyguardUserSwitcherIsShowing == open) { - return; - } - mKeyguardUserSwitcherIsShowing = open; - - if (open) { - animateKeyguardStatusBarOut(); - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - mBarState, - true /* keyguardFadingAway */, - true /* goingToFullShade */, - mBarState); - setKeyguardBottomAreaVisibility(mBarState, true); - mNotificationContainerParent.setVisibility(View.GONE); - } else { - animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - StatusBarState.KEYGUARD, - false, - false, - StatusBarState.SHADE_LOCKED); - setKeyguardBottomAreaVisibility(mBarState, false); - mNotificationContainerParent.setVisibility(View.VISIBLE); - } - } - private void updateDisabledUdfpsController() { final boolean udfpsEnrolled = mAuthController.getUdfpsRegion() != null && mAuthController.isUdfpsEnrolled( 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 041a97e1d404..b25fced6a212 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -78,6 +78,7 @@ import android.media.AudioAttributes; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -276,7 +277,8 @@ public class StatusBar extends SystemUI implements DemoMode, public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info - public static final boolean DEBUG_GESTURES = false; + public static final boolean DEBUG_GESTURES = Build.IS_DEBUGGABLE; // TODO(b/178277858) + public static final boolean DEBUG_GESTURES_VERBOSE = true; public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; public static final boolean DEBUG_CAMERA_LIFT = false; @@ -456,9 +458,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final DisplayMetrics mDisplayMetrics; // XXX: gesture research - private final GestureRecorder mGestureRec = DEBUG_GESTURES - ? new GestureRecorder("/sdcard/statusbar_gestures.dat") - : null; + private GestureRecorder mGestureRec = null; private final ScreenPinningRequest mScreenPinningRequest; @@ -856,6 +856,10 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityIntentHelper = new ActivityIntentHelper(mContext); DateTimeView.setReceiverHandler(timeTickHandler); + + if (DEBUG_GESTURES) { + mGestureRec = new GestureRecorder(mContext.getCacheDir() + "/statusbar_gestures.dat"); + } } @Override @@ -2267,7 +2271,7 @@ public class StatusBar extends SystemUI implements DemoMode, public boolean interceptTouchEvent(MotionEvent event) { if (DEBUG_GESTURES) { - if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { + if (DEBUG_GESTURES_VERBOSE || event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled1, mDisabled2); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index a76d08a438f2..7f935d28285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.policy; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); - boolean isSensorBlocked(@IndividualSensor int sensor); + boolean isSensorBlocked(@Sensor int sensor); - void setSensorBlocked(@IndividualSensor int sensor, boolean blocked); + void setSensorBlocked(@Sensor int sensor, boolean blocked); interface Callback { - void onSensorBlockedChanged(@IndividualSensor int sensor, boolean blocked); + void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 32d15ed41648..295df05797ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.policy; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.util.ArraySet; import android.util.SparseBooleanArray; @@ -30,8 +30,7 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { - private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, - INDIVIDUAL_SENSOR_MICROPHONE}; + private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); @@ -48,18 +47,18 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mSensorPrivacyManager.addSensorPrivacyListener(sensor, (enabled) -> onSensorPrivacyChanged(sensor, enabled)); - mState.put(sensor, mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)); + mState.put(sensor, mSensorPrivacyManager.isSensorPrivacyEnabled(sensor)); } } @Override - public boolean isSensorBlocked(@IndividualSensor int sensor) { + public boolean isSensorBlocked(@Sensor int sensor) { return mState.get(sensor, false); } @Override - public void setSensorBlocked(@IndividualSensor int sensor, boolean blocked) { - mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, blocked); + public void setSensorBlocked(@Sensor int sensor, boolean blocked) { + mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked); } @Override @@ -72,7 +71,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mCallbacks.remove(listener); } - private void onSensorPrivacyChanged(@IndividualSensor int sensor, boolean blocked) { + private void onSensorPrivacyChanged(@Sensor int sensor, boolean blocked) { mState.put(sensor, blocked); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index b76e451cb681..8845a05cf6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -19,9 +19,13 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.database.DataSetObserver; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.UserHandle; @@ -50,7 +54,6 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.ViewController; -import java.lang.ref.WeakReference; import java.util.ArrayList; import javax.inject.Inject; @@ -73,9 +76,10 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS private final KeyguardUserAdapter mAdapter; private final KeyguardStateController mKeyguardStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback; protected final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private ObjectAnimator mBgAnimator; + private final KeyguardUserSwitcherScrim mBackground; // Child views of KeyguardUserSwitcherView private KeyguardUserSwitcherListView mListView; @@ -171,6 +175,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mUserSwitcherController, this); mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters); + mBackground = new KeyguardUserSwitcherScrim(context); } @Override @@ -204,6 +209,9 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mScreenLifecycle.addObserver(mScreenObserver); + mView.addOnLayoutChangeListener(mBackground); + mView.setBackground(mBackground); + mBackground.setAlpha(0); } @Override @@ -217,6 +225,9 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardUpdateMonitor.removeCallback(mInfoCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); mScreenLifecycle.removeObserver(mScreenObserver); + mView.removeOnLayoutChangeListener(mBackground); + mView.setBackground(null); + mBackground.setAlpha(0); } /** @@ -338,6 +349,13 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS animate); PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), ANIMATION_PROPERTIES, animate); + + Rect r = new Rect(); + mListView.getDrawingRect(r); + mView.offsetDescendantRectToMyCoords(mListView, r); + mBackground.setGradientCenter( + (int) (mListView.getTranslationX() + r.left + r.width() / 2), + (int) (mListView.getTranslationY() + r.top + r.height() / 2)); } /** @@ -372,49 +390,52 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } /** - * Remove the callback if it exists. - */ - public void removeCallback() { - if (DEBUG) Log.d(TAG, "removeCallback"); - mKeyguardUserSwitcherCallback = null; - } - - /** - * Register to receive notifications about keyguard user switcher state - * (see {@link KeyguardUserSwitcherListener}. - * - * Only one callback can be used at a time. - * - * @param callback The callback to register - */ - public void setCallback(KeyguardUserSwitcherListener callback) { - if (DEBUG) Log.d(TAG, "setCallback"); - mKeyguardUserSwitcherCallback = new WeakReference<>(callback); - } - - /** - * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}. - * Switcher state is updatd before animations finish. + * NOTE: switcher state is updated before animations finish. * * @param animate true to animate transition. The user switcher state (i.e. * {@link #isUserSwitcherOpen()}) is updated before animation is finished. */ private void setUserSwitcherOpened(boolean open, boolean animate) { - boolean wasOpen = mUserSwitcherOpen; if (DEBUG) { - Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen, - open, animate)); + Log.d(TAG, + String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", + mUserSwitcherOpen, open, animate)); } mUserSwitcherOpen = open; - if (mUserSwitcherOpen != wasOpen) { - notifyUserSwitcherStateChanged(); - } updateVisibilities(animate); } private void updateVisibilities(boolean animate) { if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate)); mEndGuestButton.animate().cancel(); + if (mBgAnimator != null) { + mBgAnimator.cancel(); + } + + if (mUserSwitcherOpen) { + mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255); + mBgAnimator.setDuration(400); + mBgAnimator.setInterpolator(Interpolators.ALPHA_IN); + mBgAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBgAnimator = null; + } + }); + mBgAnimator.start(); + } else { + mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0); + mBgAnimator.setDuration(400); + mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT); + mBgAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBgAnimator = null; + } + }); + mBgAnimator.start(); + } + if (mUserSwitcherOpen && mCurrentUserIsGuest) { // Show the "End guest session" button mEndGuestButton.setVisibility(View.VISIBLE); @@ -459,34 +480,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS return mUserSwitcherOpen; } - private void notifyUserSwitcherStateChanged() { - if (DEBUG) { - Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b", - mUserSwitcherOpen)); - } - if (mKeyguardUserSwitcherCallback != null) { - KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get(); - if (cb != null) { - cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen); - } - } - } - - /** - * Callback for keyguard user switcher state information - */ - public interface KeyguardUserSwitcherListener { - - /** - * Called when the keyguard enters or leaves user switcher mode. This will be called - * before the animations are finished. - * - * @param open if true, keyguard is showing the user switcher or transitioning from/to user - * switcher mode. - */ - void onKeyguardUserSwitcherChanged(boolean open); - } - static class KeyguardUserAdapter extends UserSwitcherController.BaseUserAdapter implements View.OnClickListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java index 49f5bcdd5a44..1d9d33d2aab1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java @@ -26,7 +26,6 @@ import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.util.LayoutDirection; import android.view.View; import com.android.systemui.R; @@ -38,13 +37,14 @@ public class KeyguardUserSwitcherScrim extends Drawable implements View.OnLayoutChangeListener { private static final float OUTER_EXTENT = 2.5f; - private static final float INNER_EXTENT = 0.75f; + private static final float INNER_EXTENT = 0.25f; private int mDarkColor; - private int mTop; private int mAlpha = 255; private Paint mRadialGradientPaint = new Paint(); - private int mLayoutWidth; + private int mCircleX; + private int mCircleY; + private int mSize; public KeyguardUserSwitcherScrim(Context context) { mDarkColor = context.getColor( @@ -53,14 +53,11 @@ public class KeyguardUserSwitcherScrim extends Drawable @Override public void draw(Canvas canvas) { - boolean isLtr = getLayoutDirection() == LayoutDirection.LTR; + if (mAlpha == 0) { + return; + } Rect bounds = getBounds(); - float width = bounds.width() * OUTER_EXTENT; - float height = (mTop + bounds.height()) * OUTER_EXTENT; - canvas.translate(0, -mTop); - canvas.scale(1, height / width); - canvas.drawRect(isLtr ? bounds.right - width : 0, 0, - isLtr ? bounds.right : bounds.left + width, width, mRadialGradientPaint); + canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, mRadialGradientPaint); } @Override @@ -88,24 +85,36 @@ public class KeyguardUserSwitcherScrim extends Drawable public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { - mLayoutWidth = right - left; - mTop = top; + int width = right - left; + int height = bottom - top; + mSize = Math.max(width, height); updatePaint(); } } private void updatePaint() { - if (mLayoutWidth == 0) { + if (mSize == 0) { return; } - float radius = mLayoutWidth * OUTER_EXTENT; - boolean isLtr = getLayoutDirection() == LayoutDirection.LTR; + float outerRadius = mSize * OUTER_EXTENT; mRadialGradientPaint.setShader( - new RadialGradient(isLtr ? mLayoutWidth : 0, 0, radius, + new RadialGradient(mCircleX, mCircleY, outerRadius, new int[] { Color.argb( (int) (Color.alpha(mDarkColor) * mAlpha / 255f), 0, 0, 0), Color.TRANSPARENT }, - new float[] { Math.max(0f, mLayoutWidth * INNER_EXTENT / radius), 1f }, + new float[] { Math.max(0f, INNER_EXTENT / OUTER_EXTENT), 1f }, Shader.TileMode.CLAMP)); } + + /** + * Sets the center of the radial gradient used as a background + * + * @param x + * @param y + */ + public void setGradientCenter(int x, int y) { + mCircleX = x; + mCircleY = y; + updatePaint(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 0a3e83326e01..bbb2f1a5259a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -94,6 +94,7 @@ public class ThemeOverlayApplier implements Dumpable { */ static final List<String> THEME_CATEGORIES = Lists.newArrayList( OVERLAY_CATEGORY_SYSTEM_PALETTE, + OVERLAY_CATEGORY_NEUTRAL_PALETTE, OVERLAY_CATEGORY_ICON_LAUNCHER, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_FONT, @@ -107,6 +108,7 @@ public class ThemeOverlayApplier implements Dumpable { @VisibleForTesting static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet( OVERLAY_CATEGORY_SYSTEM_PALETTE, + OVERLAY_CATEGORY_NEUTRAL_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, @@ -129,8 +131,9 @@ public class ThemeOverlayApplier implements Dumpable { mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( - OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, - OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID)); + OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, + OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, + OVERLAY_CATEGORY_ICON_ANDROID)); mTargetPackageToCategories.put(SYSUI_PACKAGE, Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI)); mTargetPackageToCategories.put(SETTINGS_PACKAGE, @@ -158,10 +161,10 @@ public class ThemeOverlayApplier implements Dumpable { void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, - Set<UserHandle> userHandles) { + int currentUser, + Set<UserHandle> managedProfiles) { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); - overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()); @@ -172,6 +175,7 @@ public class ThemeOverlayApplier implements Dumpable { .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) + .filter(o -> !categoryToPackage.containsValue(new OverlayIdentifier(o.packageName))) .filter(o -> o.isEnabled()) .map(o -> new Pair<>(o.category, o.packageName)) .collect(Collectors.toList()); @@ -183,17 +187,18 @@ public class ThemeOverlayApplier implements Dumpable { } } - // Toggle overlays in the order of THEME_CATEGORIES. + for (Pair<String, String> packageToDisable : overlaysToDisable) { + OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); + setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser, + managedProfiles, false); + } + for (String category : THEME_CATEGORIES) { if (categoryToPackage.containsKey(category)) { OverlayIdentifier overlayInfo = categoryToPackage.get(category); - setEnabled(transaction, overlayInfo, category, userHandles, true); + setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, true); } } - for (Pair<String, String> packageToDisable : overlaysToDisable) { - OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); - setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false); - } mExecutor.execute(() -> { try { @@ -210,18 +215,30 @@ public class ThemeOverlayApplier implements Dumpable { } private void setEnabled(OverlayManagerTransaction.Builder transaction, - OverlayIdentifier identifier, String category, Set<UserHandle> handles, - boolean enabled) { + OverlayIdentifier identifier, String category, int currentUser, + Set<UserHandle> managedProfiles, boolean enabled) { if (DEBUG) { Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " + category + ": " + enabled); } - for (UserHandle userHandle : handles) { - transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); - } - if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { + + transaction.setEnabled(identifier, enabled, currentUser); + if (currentUser != UserHandle.SYSTEM.getIdentifier() + && SYSTEM_USER_CATEGORIES.contains(category)) { transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); } + + // Do not apply Launcher or Theme picker overlays to managed users. Apps are not + // installed in there. + OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM); + if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage) + || overlayInfo.targetPackageName.equals(mThemePickerPackage)) { + return; + } + + for (UserHandle userHandle : managedProfiles) { + transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 1f222d80f014..5d028454a417 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -55,14 +55,13 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; -import com.google.android.collect.Sets; - import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -86,7 +85,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { protected static final int PRIMARY = 0; protected static final int SECONDARY = 1; - protected static final int NEUTRAL = 1; + protected static final int NEUTRAL = 2; // If lock screen wallpaper colors should also be considered when selecting the theme. // Doing this has performance impact, given that overlays would need to be swapped when @@ -151,7 +150,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } }, filter, mBgExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( @@ -163,7 +162,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } } }, @@ -180,7 +179,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mLockColors = lockColors; } mSystemColors = systemColor; - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }); }); if (USE_LOCK_SCREEN_WALLPAPER) { @@ -192,7 +191,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } // It's possible that the user has a lock screen wallpaper. On this case we'll // end up with different colors after unlocking. - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); } }); } @@ -209,11 +208,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); } } - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }, null, UserHandle.USER_ALL); } - private void reevaluateSystemTheme() { + private void reevaluateSystemTheme(boolean forceReload) { WallpaperColors currentColors = mKeyguardStateController.isShowing() && mLockColors != null ? mLockColors : mSystemColors; @@ -228,7 +227,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { accentCandidate = getAccentColor(currentColors); } - if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate) { + if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate + && !forceReload) { return; } @@ -309,6 +309,16 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); } + } else if (!mIsMonetEnabled && systemPalette != null) { + try { + // It's possible that we flipped the flag off and still have a @ColorInt in the + // setting. We need to sanitize the input, otherwise the overlay transaction will + // fail. + Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Same for accent color. @@ -322,6 +332,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); } + } else if (!mIsMonetEnabled && accentPalette != null) { + try { + Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Compatibility with legacy themes, where full packages were defined, instead of just @@ -337,10 +354,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier()); } - Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); + Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { - userHandles.add(userInfo.getUserHandle()); + managedProfiles.add(userInfo.getUserHandle()); } } if (DEBUG) { @@ -352,9 +369,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay - }, userHandles); + }, currentUser, managedProfiles); } else { - mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); + mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, + managedProfiles); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index df54eabca8e7..25345d5c4b4c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -33,7 +33,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Dialog; @@ -51,6 +55,9 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RotateDrawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -81,6 +88,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; @@ -88,6 +97,7 @@ import android.widget.Toast; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -99,6 +109,8 @@ import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.AlphaTintDrawableWrapper; +import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,8 +136,14 @@ public class VolumeDialogImpl implements VolumeDialog, static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; + private static final int DRAWER_ANIMATION_DURATION = 250; + private final int mDialogShowAnimationDurationMs; private final int mDialogHideAnimationDurationMs; + private final int mRingerDrawerItemSize; + private final boolean mShowVibrate; + private final int mRingerCount; private final boolean mShowLowMediaVolumeIcon; private final boolean mChangeVolumeRowTintWhenInactive; @@ -140,6 +158,30 @@ public class VolumeDialogImpl implements VolumeDialog, private ViewGroup mDialogView; private ViewGroup mDialogRowsView; private ViewGroup mRinger; + + private ViewGroup mSelectedRingerContainer; + private ImageView mSelectedRingerIcon; + + private ViewGroup mRingerDrawerContainer; + private ViewGroup mRingerDrawerMute; + private ViewGroup mRingerDrawerVibrate; + private ViewGroup mRingerDrawerNormal; + private ImageView mRingerDrawerMuteIcon; + private ImageView mRingerDrawerVibrateIcon; + private ImageView mRingerDrawerNormalIcon; + + /** + * View that draws the 'selected' background behind one of the three ringer choices in the + * drawer. + */ + private ViewGroup mRingerDrawerNewSelectionBg; + + private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); + private ImageView mRingerDrawerIconAnimatingSelected; + private ImageView mRingerDrawerIconAnimatingDeselected; + + private boolean mIsRingerDrawerOpen = false; + private ImageButton mRingerIcon; private ViewGroup mODICaptionsView; private CaptionsToggleImageButton mODICaptionsIcon; @@ -191,6 +233,12 @@ public class VolumeDialogImpl implements VolumeDialog, mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); mDialogHideAnimationDurationMs = mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); + mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( + R.dimen.volume_ringer_drawer_item_size); + mShowVibrate = mController.hasVibrator(); + + // Normal, mute, and possibly vibrate. + mRingerCount = mShowVibrate ? 3 : 2; } @Override @@ -314,6 +362,20 @@ public class VolumeDialogImpl implements VolumeDialog, mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); + mSelectedRingerContainer = mDialog.findViewById( + R.id.volume_new_ringer_active_icon_container); + + mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); + mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); + mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); + mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); + mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); + mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); + mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); + + setupRingerDrawer(); + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); @@ -475,38 +537,273 @@ public class VolumeDialogImpl implements VolumeDialog, row.anim = null; + final LayerDrawable seekbarDrawable = + (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); + + final LayerDrawable seekbarBgDrawable = + (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background); + + row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_solid); + + row.sliderBgIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon)).getDrawable(); + + final LayerDrawable seekbarProgressDrawable = (LayerDrawable) + ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( + android.R.id.progress)).getDrawable(); + + row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_solid); + + row.sliderProgressIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon)).getDrawable(); + + row.slider.setProgressDrawable(seekbarDrawable); + row.slider.setThumb(null); + row.icon = row.view.findViewById(R.id.volume_row_icon); - row.icon.setImageResource(iconRes); - if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(v -> { - Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + + row.setIcon(iconRes, mContext.getTheme()); + + if (row.icon != null) { + if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { + row.icon.setOnClickListener(v -> { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); + } } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); + mController.setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } - } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); - } - row.userAttempt = 0; // reset the grace period, slider updates immediately - }); + row.userAttempt = 0; // reset the grace period, slider updates immediately + }); + } else { + row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + } + } + + private void setRingerMode(int newRingerMode) { + Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); + incrementManualToggleCount(); + updateRingerH(); + provideTouchFeedbackH(newRingerMode); + mController.setRingerMode(newRingerMode, false); + maybeShowToastH(newRingerMode); + } + + private void setupRingerDrawer() { + mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); + + if (mRingerDrawerContainer == null) { + return; + } + + if (!mShowVibrate) { + mRingerDrawerVibrate.setVisibility(GONE); + } + + // In portrait, add padding to the bottom to account for the height of the open ringer + // drawer. + if (!isLandscape()) { + mDialogView.setPadding( + mDialogView.getPaddingLeft(), + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize); + } else { + mDialogView.setPadding( + mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize, + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom()); + } + + ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) + .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mSelectedRingerContainer.setOnClickListener(view -> { + if (mIsRingerDrawerOpen) { + hideRingerDrawer(); + } else { + showRingerDrawer(); + } + }); + + mRingerDrawerVibrate.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); + mRingerDrawerMute.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); + mRingerDrawerNormal.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); + + final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); + final int selectedColor = Utils.getColorAttrDefaultColor( + mContext, android.R.attr.colorBackgroundFloating); + + // Add an update listener that animates the deselected icon to the unselected color, and the + // selected icon to the selected color. + mRingerDrawerIconColorAnimator.addUpdateListener( + anim -> { + final float currentValue = (float) anim.getAnimatedValue(); + final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, selectedColor, unselectedColor); + final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, unselectedColor, selectedColor); + + mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); + mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); + }); + mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRingerDrawerIconAnimatingDeselected.clearColorFilter(); + mRingerDrawerIconAnimatingSelected.clearColorFilter(); + } + }); + mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); + } + + private ImageView getDrawerIconViewForMode(int mode) { + if (mode == RINGER_MODE_VIBRATE) { + return mRingerDrawerVibrateIcon; + } else if (mode == RINGER_MODE_SILENT) { + return mRingerDrawerMuteIcon; + } else { + return mRingerDrawerNormalIcon; + } + } + + /** + * Translation to apply form the origin (either top or left) to overlap the selection background + * with the given mode in the drawer. + */ + private float getTranslationInDrawerForRingerMode(int mode) { + return mode == RINGER_MODE_VIBRATE + ? -mRingerDrawerItemSize * 2 + : mode == RINGER_MODE_SILENT + ? -mRingerDrawerItemSize + : 0; + } + + /** Animates in the ringer drawer. */ + private void showRingerDrawer() { + // Show all ringer icons except the currently selected one, since we're going to animate the + // ringer button to that position. + mRingerDrawerVibrateIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); + mRingerDrawerMuteIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); + mRingerDrawerNormalIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); + + // Hide the selection background - we use this to show a selection when one is + // tapped, so it should be invisible until that happens. However, position it below + // the currently selected ringer so that it's ready to animate. + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.setTranslationY( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } else { + mRingerDrawerNewSelectionBg.setTranslationX( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } + + // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer + // icon. + if (!isLandscape()) { + mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); + } else { + mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); + } + mRingerDrawerContainer.setAlpha(0f); + mRingerDrawerContainer.setVisibility(VISIBLE); + + // Animate the drawer up and visible. + mRingerDrawerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + // Vibrate is way farther up, so give the selected ringer icon a head start if + // vibrate is selected. + .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION_SHORT + : DRAWER_ANIMATION_DURATION) + .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT + : 0) + .alpha(1f) + .translationX(0f) + .translationY(0f) + .start(); + + // Animate the selected ringer view up to that ringer's position in the drawer. + mSelectedRingerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(DRAWER_ANIMATION_DURATION) + .withEndAction(() -> + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); + + if (!isLandscape()) { + mSelectedRingerContainer.animate() + .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } else { - row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mSelectedRingerContainer.animate() + .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } + + mIsRingerDrawerOpen = true; + } + + /** Animates away the ringer drawer. */ + private void hideRingerDrawer() { + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we + // don't want to be able to see it while it animates away. + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); + + mRingerDrawerContainer.animate() + .alpha(0f) + .setDuration(DRAWER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); + + if (!isLandscape()) { + mRingerDrawerContainer.animate() + .translationY(mRingerDrawerItemSize * 2) + .start(); + } else { + mRingerDrawerContainer.animate() + .translationX(mRingerDrawerItemSize * 2) + .start(); + } + + mSelectedRingerContainer.animate() + .translationX(0f) + .translationY(0f) + .start(); + + mIsRingerDrawerOpen = false; } public void initSettingsH() { @@ -555,12 +852,8 @@ public class VolumeDialogImpl implements VolumeDialog, mController.setStreamVolume(AudioManager.STREAM_RING, 1); } } - Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); - incrementManualToggleCount(); - updateRingerH(); - provideTouchFeedbackH(newRingerMode); - mController.setRingerMode(newRingerMode, false); - maybeShowToastH(newRingerMode); + + setRingerMode(newRingerMode); }); } updateRingerH(); @@ -809,6 +1102,8 @@ public class VolumeDialogImpl implements VolumeDialog, mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.start(); @@ -889,12 +1184,14 @@ public class VolumeDialogImpl implements VolumeDialog, switch (mState.ringerModeInternal) { case AudioManager.RINGER_MODE_VIBRATE: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, mContext.getString(R.string.volume_ringer_hint_mute)); mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); break; case AudioManager.RINGER_MODE_SILENT: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); mRingerIcon.setTag(Events.ICON_STATE_MUTE); addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, mContext.getString(R.string.volume_ringer_hint_unmute)); @@ -904,11 +1201,13 @@ public class VolumeDialogImpl implements VolumeDialog, boolean muted = (mAutomute && ss.level == 0) || ss.muted; if (!isZenMuted && muted) { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_unmute)); mRingerIcon.setTag(Events.ICON_STATE_MUTE); } else { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); if (mController.hasVibrator()) { addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_vibrate)); @@ -1075,8 +1374,6 @@ public class VolumeDialogImpl implements VolumeDialog, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; - row.icon.setEnabled(iconEnabled); - row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes; if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; @@ -1092,7 +1389,7 @@ public class VolumeDialogImpl implements VolumeDialog, ? R.drawable.ic_volume_media_low : row.iconRes; } - row.icon.setImageResource(iconRes); + row.setIcon(iconRes, mContext.getTheme()); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -1101,18 +1398,35 @@ public class VolumeDialogImpl implements VolumeDialog, || iconRes == R.drawable.ic_volume_media_low) ? Events.ICON_STATE_UNMUTE : Events.ICON_STATE_UNKNOWN; - if (iconEnabled) { - if (isRingStream) { - if (isRingVibrate) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); + + if (row.icon != null) { + if (iconEnabled) { + if (isRingStream) { + if (isRingVibrate) { + row.icon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + } else { + if (mController.hasVibrator()) { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + } else { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_mute_a11y + : R.string.volume_stream_content_description_mute, + getStreamLabelH(ss))); + } + } + } else if (isA11yStream) { + row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (mController.hasVibrator()) { + if (ss.muted || mAutomute && ss.level == 0) { row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_vibrate_a11y - : R.string.volume_stream_content_description_vibrate, + R.string.volume_stream_content_description_unmute, getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( @@ -1122,23 +1436,9 @@ public class VolumeDialogImpl implements VolumeDialog, getStreamLabelH(ss))); } } - } else if (isA11yStream) { - row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (ss.muted || mAutomute && ss.level == 0) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_mute_a11y - : R.string.volume_stream_content_description_mute, - getStreamLabelH(ss))); - } + row.icon.setContentDescription(getStreamLabelH(ss)); } - } else { - row.icon.setContentDescription(getStreamLabelH(ss)); } // ensure tracking is disabled if zenMuted @@ -1167,22 +1467,29 @@ public class VolumeDialogImpl implements VolumeDialog, if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { return; } - final ColorStateList tint = useActiveColoring + final ColorStateList colorTint = useActiveColoring ? Utils.getColorAccent(mContext) : Utils.getColorAttr(mContext, android.R.attr.colorForeground); final int alpha = useActiveColoring - ? Color.alpha(tint.getDefaultColor()) + ? Color.alpha(colorTint.getDefaultColor()) : getAlphaAttr(android.R.attr.secondaryContentAlpha); - if (tint == row.cachedTint) return; - row.slider.setProgressTintList(tint); - row.slider.setThumbTintList(tint); - row.slider.setProgressBackgroundTintList(tint); - row.slider.setAlpha(((float) alpha) / 255); - row.icon.setImageTintList(tint); - row.icon.setImageAlpha(alpha); - row.cachedTint = tint; + + final ColorStateList bgTint = Utils.getColorAttr( + mContext, android.R.attr.colorBackgroundFloating); + + row.sliderProgressSolid.setTintList(colorTint); + row.sliderBgIcon.setTintList(colorTint); + + row.sliderBgSolid.setTintList(bgTint); + row.sliderProgressIcon.setTintList(bgTint); + + if (row.icon != null) { + row.icon.setImageTintList(colorTint); + row.icon.setImageAlpha(alpha); + } + if (row.number != null) { - row.number.setTextColor(tint); + row.number.setTextColor(colorTint); row.number.setAlpha(alpha); } } @@ -1538,6 +1845,10 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; + private Drawable sliderBgSolid; + private AlphaTintDrawableWrapper sliderBgIcon; + private Drawable sliderProgressSolid; + private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; private TextView number; private int stream; @@ -1555,5 +1866,69 @@ public class VolumeDialogImpl implements VolumeDialog, private int animTargetProgress; private int lastAudibleLevel = 1; private FrameLayout dndIcon; + + void setIcon(int iconRes, Resources.Theme theme) { + if (icon != null) { + icon.setImageResource(iconRes); + } + + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } + } + + /** + * Click listener added to each ringer option in the drawer. This will initiate the animation to + * select and then close the ringer drawer, and actually change the ringer mode. + */ + private class RingerDrawerItemClickListener implements View.OnClickListener { + private final int mClickedRingerMode; + + RingerDrawerItemClickListener(int clickedRingerMode) { + mClickedRingerMode = clickedRingerMode; + } + + @Override + public void onClick(View view) { + setRingerMode(mClickedRingerMode); + + mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); + mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( + mState.ringerModeInternal); + + // Begin switching the selected icon and deselected icon colors since the background is + // going to animate behind the new selection. + mRingerDrawerIconColorAnimator.start(); + + mSelectedRingerContainer.setVisibility(View.INVISIBLE); + mRingerDrawerNewSelectionBg.setAlpha(1f); + mRingerDrawerNewSelectionBg.animate() + .setInterpolator(Interpolators.ACCELERATE_DECELERATE) + .setDuration(DRAWER_ANIMATION_DURATION_SHORT) + .withEndAction(() -> { + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mSelectedRingerContainer.setTranslationY( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } else { + mSelectedRingerContainer.setTranslationX( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } + + mSelectedRingerContainer.setVisibility(VISIBLE); + hideRingerDrawer(); + }); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.animate() + .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } else { + mRingerDrawerNewSelectionBg.animate() + .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 5d9465904e3b..4611fe03e157 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -31,6 +31,7 @@ import android.view.WindowManager; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.R; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -97,7 +98,12 @@ import dagger.Provides; @Module public abstract class WMShellBaseModule { - private static final boolean ENABLE_SHELL_MAIN_THREAD = false; + /** + * Returns whether to enable a separate shell thread for the shell features. + */ + private static boolean enableShellMainThread(Context context) { + return context.getResources().getBoolean(R.bool.config_enableShellMainThread); + } // // Shell Concurrency - Components used for managing threading in the Shell and SysUI @@ -120,8 +126,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides @ShellMainThread - public static Handler provideShellMainHandler(@Main Handler sysuiMainHandler) { - if (ENABLE_SHELL_MAIN_THREAD) { + public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { + if (enableShellMainThread(context)) { HandlerThread mainThread = new HandlerThread("wmshell.main"); mainThread.start(); return mainThread.getThreadHandler(); @@ -135,9 +141,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides @ShellMainThread - public static ShellExecutor provideShellMainExecutor(@ShellMainThread Handler mainHandler, - @Main ShellExecutor sysuiMainExecutor) { - if (ENABLE_SHELL_MAIN_THREAD) { + public static ShellExecutor provideShellMainExecutor(Context context, + @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { + if (enableShellMainThread(context)) { return new HandlerExecutor(mainHandler); } return sysuiMainExecutor; @@ -409,10 +415,11 @@ public abstract class WMShellBaseModule { ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Context context, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayImeController displayImeController) { if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context, - rootTaskDisplayAreaOrganizer, mainExecutor)); + rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController)); } else { return Optional.empty(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 997b488a627f..754b6a6435b4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -104,9 +104,10 @@ public class WMShellModule { @Provides static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, DisplayController displayController, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayImeController displayImeController) { return new AppPairsController(shellTaskOrganizer, syncQueue, displayController, - mainExecutor); + mainExecutor, displayImeController); } // diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 14b4d0262f60..854be1f76722 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -26,16 +26,11 @@ import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.testing.TestableResources; -import android.view.View; -import android.view.ViewGroup; import android.view.WindowInsetsController; -import android.widget.FrameLayout; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import org.junit.Before; @@ -50,21 +45,12 @@ import org.mockito.junit.MockitoRule; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { - private static final int SCREEN_WIDTH = 1600; - private static final int FAKE_MEASURE_SPEC = - View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, View.MeasureSpec.EXACTLY); - - private static final SecurityMode ONE_HANDED_SECURITY_MODE = SecurityMode.PIN; - private static final SecurityMode TWO_HANDED_SECURITY_MODE = SecurityMode.Password; - - @Rule public MockitoRule mRule = MockitoJUnit.rule(); @Mock private WindowInsetsController mWindowInsetsController; - @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; @@ -72,18 +58,9 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Before public void setup() { - // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache - // the real references (rather than the TestableResources that this call creates). - mContext.ensureTestableResources(); - FrameLayout.LayoutParams securityViewFlipperLayoutParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(securityViewFlipperLayoutParams); mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; - mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } @Test @@ -92,75 +69,4 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(), any(), any()); } - - @Test - public void onMeasure_usesFullWidthWithoutOneHandedMode() { - setUpKeyguard( - /* deviceConfigCanUseOneHandedKeyguard= */false, - /* sysuiResourceCanUseOneHandedKeyguard= */ false, - ONE_HANDED_SECURITY_MODE); - - mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - - verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - } - - @Test - public void onMeasure_usesFullWidthWithDeviceFlagDisabled() { - setUpKeyguard( - /* deviceConfigCanUseOneHandedKeyguard= */false, - /* sysuiResourceCanUseOneHandedKeyguard= */ true, - ONE_HANDED_SECURITY_MODE); - - mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - } - - @Test - public void onMeasure_usesFullWidthWithSysUIFlagDisabled() { - setUpKeyguard( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */ false, - ONE_HANDED_SECURITY_MODE); - - mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - } - - @Test - public void onMeasure_usesHalfWidthWithFlagsEnabled() { - setUpKeyguard( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */ true, - ONE_HANDED_SECURITY_MODE); - - int halfWidthMeasureSpec = - View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY); - mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - - verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, FAKE_MEASURE_SPEC); - } - - @Test - public void onMeasure_usesFullWidthForFullScreenIme() { - setUpKeyguard( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */ true, - TWO_HANDED_SECURITY_MODE); - - mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); - } - - private void setUpKeyguard( - boolean deviceConfigCanUseOneHandedKeyguard, - boolean sysuiResourceCanUseOneHandedKeyguard, - SecurityMode securityMode) { - TestableResources testableResources = mContext.getOrCreateTestableResources(); - testableResources.addOverride(com.android.internal.R.bool.config_enableOneHandedKeyguard, - deviceConfigCanUseOneHandedKeyguard); - testableResources.addOverride(R.bool.can_use_one_handed_bouncer, - sysuiResourceCanUseOneHandedKeyguard); - mKeyguardSecurityContainer.updateLayoutForSecurityMode(securityMode); - } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 97cb8736f01c..2526990dfd40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static junit.framework.TestCase.assertFalse; @@ -125,9 +125,9 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); mController = new AppOpsControllerImpl( @@ -505,7 +505,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add a camera op, and disable the microphone. The camera op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true); + mController.onSensorBlockedChanged(MICROPHONE, true); mController.onOpActiveChanged( AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -515,7 +515,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Re enable the microphone, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false); + mController.onSensorBlockedChanged(MICROPHONE, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); @@ -538,7 +538,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add an audio op, and disable the camera. The audio op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true); + mController.onSensorBlockedChanged(CAMERA, true); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -547,7 +547,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); // Re enable the camera, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false); + mController.onSensorBlockedChanged(CAMERA, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index c8e939609e87..2a4b41cbfe32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -464,7 +464,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromNotification(mContext, tile, sbn); @@ -482,7 +482,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromNotification(mContext, tile, sbn); @@ -496,7 +496,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, @@ -511,7 +511,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, @@ -526,7 +526,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); List<PeopleSpaceTile> actualList = PeopleSpaceUtils .augmentTilesFromVisibleNotifications( @@ -545,13 +545,13 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(1) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile tile2 = new PeopleSpaceTile .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); List<PeopleSpaceTile> actualList = PeopleSpaceUtils .augmentTilesFromVisibleNotifications(mContext, List.of(tile1, tile2), diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 1c8324c524f4..800d8593035d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -109,7 +109,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID, "username", ICON, new Intent()) .setPackageName(TEST_PACKAGE_A) - .setUid(0) + .setUserHandle(new UserHandle(1)) .setNotificationKey(NOTIFICATION_KEY + "1") .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 2dfd38832997..d40e6a4586b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -89,6 +89,8 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { private View mEdit; @Mock private MultiUserSwitch mMultiUserSwitch; + @Mock + private View mPowerMenuLiteView; private QSFooterViewController mController; @@ -111,11 +113,12 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { when(mView.findViewById(R.id.build)).thenReturn(mBuildText); when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit); when(mView.findViewById(R.id.multi_user_switch)).thenReturn(mMultiUserSwitch); + when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView); mController = new QSFooterViewController(mView, mUserManager, mUserInfoController, mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController, new QSDetailDisplayer(), mQuickQSPanelController, mFakeTunerService, - mMetricsLogger); + mMetricsLogger, false); mController.init(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 0dfebab59feb..a2a179ea29df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -121,7 +121,6 @@ public class QSPanelControllerTest extends SysuiTestCase { mQSTileHost, mQSCustomizerController, true, mMediaHost, mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - /* labelsFlag */ false, mFeatureFlags ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 587020090433..cb380d51bd79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -87,7 +87,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - false, featureFlags ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java index a75c39c33f14..9e62a6263a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.view.IScrollCaptureCallbacks; import android.view.IScrollCaptureConnection; @@ -46,7 +47,7 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { } @Override - public void startCapture(Surface surface) { + public ICancellationSignal startCapture(Surface surface) { mSurface = surface; mHwuiContext = new HwuiContext(false, surface); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -56,27 +57,28 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + return null; } @Override - public void requestImage(Rect rect) { + public ICancellationSignal requestImage(Rect rect) { Canvas canvas = mHwuiContext.lockCanvas(rect.width(), rect.height()); mPaint.setColor(mColors[mNextColor]); canvas.drawRect(rect, mPaint); mNextColor = (mNextColor++) % mColors.length; - long frameNumber = mSurface.getNextFrameNumber(); mHwuiContext.unlockAndPost(canvas); try { - mCallbacks.onCaptureBufferSent(frameNumber, rect); + mCallbacks.onImageRequestCompleted(0, rect); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + return null; } @Override - public void endCapture() { + public ICancellationSignal endCapture() { try { - mCallbacks.onConnectionClosed(); + mCallbacks.onCaptureEnded(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } finally { @@ -84,6 +86,12 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { mSurface = null; mCallbacks = null; } + return null; + } + + @Override + public void close() throws RemoteException { + } // From android.view.Surface, but issues render requests synchronously with waitForPresent(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java index 580f800fbc44..802b462ec10e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import static java.util.Objects.requireNonNull; import android.content.Context; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; @@ -37,6 +36,7 @@ import android.testing.AndroidTestingRunner; import android.view.Display; import android.view.IScrollCaptureCallbacks; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -82,10 +82,11 @@ public class ScrollCaptureClientTest extends SysuiTestCase { public void testBasicClientFlow() throws RemoteException { doAnswer((Answer<Void>) invocation -> { IScrollCaptureCallbacks cb = invocation.getArgument(3); - cb.onConnected( - new FakeScrollCaptureConnection(cb), - /* scrollBounds */ new Rect(0, 0, 100, 100), - /* positionInWindow */ new Point(0, 0)); + cb.onScrollCaptureResponse(new ScrollCaptureResponse.Builder() + .setBoundsInWindow(new Rect(0, 0, 100, 100)) + .setWindowBounds(new Rect(0, 0, 100, 100)) + .setConnection(new FakeScrollCaptureConnection(cb)) + .build()); return null; }).when(mWm).requestScrollCapture(/* displayId */ anyInt(), /* token */ isNull(), /* taskId */ anyInt(), any(IScrollCaptureCallbacks.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java index 2b3ca7c00a49..6564d588f4ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java @@ -19,15 +19,14 @@ package com.android.systemui.screenshot; import static org.junit.Assert.fail; import android.content.Intent; -import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.util.Log; import android.view.Display; import android.view.IScrollCaptureCallbacks; -import android.view.IScrollCaptureConnection; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import android.view.WindowManagerGlobal; import androidx.test.filters.SmallTest; @@ -67,31 +66,27 @@ public class ScrollCaptureTest extends SysuiTestCase { wms.requestScrollCapture(Display.DEFAULT_DISPLAY, null, -1, new IScrollCaptureCallbacks.Stub() { @Override - public void onConnected( - IScrollCaptureConnection connection, Rect scrollBounds, - Point positionInWindow) { - Log.d(TAG, - "client connected: " + connection + "[scrollBounds= " - + scrollBounds + ", " - + "positionInWindow=" + positionInWindow + "]"); + public void onScrollCaptureResponse(ScrollCaptureResponse response) + throws RemoteException { + Log.d(TAG, "onScrollCaptureResponse: " + response); latch.countDown(); } @Override - public void onUnavailable() { - } - - @Override public void onCaptureStarted() { } @Override - public void onCaptureBufferSent(long frameNumber, Rect capturedArea) { + public void onImageRequestCompleted(int i, Rect rect) + throws RemoteException { + } @Override - public void onConnectionClosed() { + public void onCaptureEnded() throws RemoteException { + } + }); } catch (RemoteException e) { Log.e(TAG, "request failed", e); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index ce0f1220fc88..9a5482c33501 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -377,8 +377,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN we send the dismissal to system server verify(mStatusBarService).onNotificationClear( notif2.sbn.getPackageName(), - notif2.sbn.getTag(), - 88, notif2.sbn.getUser().getIdentifier(), notif2.sbn.getKey(), stats.dismissalSurface, @@ -528,8 +526,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN we never send the dismissal to system server verify(mStatusBarService, never()).onNotificationClear( notif.sbn.getPackageName(), - notif.sbn.getTag(), - 47, notif.sbn.getUser().getIdentifier(), notif.sbn.getKey(), stats.dismissalSurface, @@ -566,8 +562,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN the notification is never sent to system server to dismiss verify(mStatusBarService, never()).onNotificationClear( eq(notif.sbn.getPackageName()), - eq(notif.sbn.getTag()), - eq(47), eq(notif.sbn.getUser().getIdentifier()), eq(notif.sbn.getKey()), anyInt(), @@ -596,8 +590,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN we send the dismissal to system server verify(mStatusBarService).onNotificationClear( eq(notif.sbn.getPackageName()), - eq(notif.sbn.getTag()), - eq(47), eq(notif.sbn.getUser().getIdentifier()), eq(notif.sbn.getKey()), anyInt(), @@ -1125,8 +1117,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN we send the dismissals to system server verify(mStatusBarService).onNotificationClear( notif1.sbn.getPackageName(), - notif1.sbn.getTag(), - 47, notif1.sbn.getUser().getIdentifier(), notif1.sbn.getKey(), stats1.dismissalSurface, @@ -1135,8 +1125,6 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mStatusBarService).onNotificationClear( notif2.sbn.getPackageName(), - notif2.sbn.getTag(), - 88, notif2.sbn.getUser().getIdentifier(), notif2.sbn.getKey(), stats2.dismissalSurface, diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index 45828c3f73ad..6067b42e0ef8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -23,6 +23,7 @@ import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_IC import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE; @@ -31,7 +32,6 @@ import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE; import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -87,7 +87,9 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String THEMEPICKER_PACKAGE = "com.android.wallpaper"; private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; private static final UserHandle TEST_USER = UserHandle.of(5); - private static final Set<UserHandle> TEST_USER_HANDLES = Sets.newHashSet(TEST_USER); + private static final UserHandle TEST_USER_MANAGED_PROFILE = UserHandle.of(6); + private static final Set<UserHandle> TEST_USER_HANDLES = + Sets.newHashSet(TEST_USER_MANAGED_PROFILE); @Mock OverlayManager mOverlayManager; @@ -114,6 +116,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE, ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false), + createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE, + ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, @@ -124,6 +128,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE, ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true), + createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE, + ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, @@ -154,13 +160,19 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER, THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true))); + + OverlayInfo launcherTargetInfo = new OverlayInfo("packageName", LAUNCHER_PACKAGE, + null, null, "/", 0, 0, 0, false); + when(mOverlayManager.getOverlayInfo(any(OverlayIdentifier.class), any())) + .thenReturn(launcherTargetInfo); clearInvocations(mOverlayManager); verify(mDumpManager).registerDumpable(any(), any()); } @Test public void allCategoriesSpecified_allEnabledExclusively() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mOverlayManager).commit(any()); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { @@ -171,7 +183,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { @@ -187,27 +200,25 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + userHandles); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), eq(TEST_USER.getIdentifier())); - verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), - eq(newUserHandle.getIdentifier())); + // Not enabled for work profile because the target package is LAUNCHER_PACKAGE + verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER_MANAGED_PROFILE.getIdentifier())); } } @Test public void applyCurrentUserOverlays_createsPendingOverlays() { - Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] { + FabricatedOverlay[] pendingCreation = new FabricatedOverlay[]{ mock(FabricatedOverlay.class) }; - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, + TEST_USER.getIdentifier(), TEST_USER_HANDLES); for (FabricatedOverlay overlay : pendingCreation) { verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); @@ -215,20 +226,13 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { } @Test - public void allCategoriesSpecified_overlayManagerNotQueried() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); - - verify(mOverlayManager, never()) - .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); - } - - @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -244,7 +248,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { - mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -258,7 +263,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), @@ -268,23 +274,6 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { eq(TEST_USER.getIdentifier())); } - @Test - public void overlayManagerOnlyQueriedForUnspecifiedPackages() { - Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); - categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); - - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); - - verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(LAUNCHER_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(THEMEPICKER_PACKAGE, - UserHandle.SYSTEM); - } - private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { return new OverlayInfo(packageName, null, targetPackageName, null, category, "", diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index aa385effa931..d80c40fcd07b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -143,7 +143,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -175,7 +175,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -198,7 +198,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c0856892dc44..3cea17567173 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.KeyguardManager; @@ -37,6 +38,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; @@ -58,6 +61,11 @@ import java.util.function.Predicate; public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; + View mActiveRinger; + View mDrawerContainer; + View mDrawerVibrate; + View mDrawerMute; + View mDrawerNormal; @Mock VolumeDialogController mController; @@ -80,6 +88,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); + + mActiveRinger = mDialog.getDialogView().findViewById( + R.id.volume_new_ringer_active_icon_container); + mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container); + mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate); + mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute); + mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal); + + Prefs.putInt(mContext, + Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, + VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); } private State createShellState() { @@ -207,6 +226,48 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mController, never()).vibrate(any()); } + @Test + public void testSelectVibrateFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerVibrate.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_VIBRATE, false); + } + + @Test + public void testSelectMuteFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerMute.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_SILENT, false); + } + + @Test + public void testSelectNormalFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerNormal.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + } + /* @Test public void testContentDescriptions() { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java index 6dcad255eee4..3502baa99b6d 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java @@ -23,7 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.ConnectivityManager; +import android.net.VpnManager; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; @@ -42,7 +42,7 @@ public class AlwaysOnDisconnectedDialog extends AlertActivity private static final String TAG = "VpnDisconnected"; - private ConnectivityManager mService; + private VpnManager mService; private int mUserId; private String mVpnPackage; @@ -51,8 +51,8 @@ public class AlwaysOnDisconnectedDialog extends AlertActivity super.onCreate(savedInstanceState); mUserId = UserHandle.myUserId(); - final ConnectivityManager cm = getSystemService(ConnectivityManager.class); - mVpnPackage = cm.getAlwaysOnVpnPackageForUser(mUserId); + final VpnManager vm = getSystemService(VpnManager.class); + mVpnPackage = vm.getAlwaysOnVpnPackageForUser(mUserId); if (mVpnPackage == null) { finish(); return; diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index aab01d03b96d..fb2367843fc1 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.net.ConnectivityManager; import android.net.VpnManager; import android.os.Bundle; import android.os.UserHandle; @@ -45,7 +44,6 @@ public class ConfirmDialog extends AlertActivity private String mPackage; - private ConnectivityManager mCm; // TODO: switch entirely to VpnManager once VPN code moves private VpnManager mVm; public ConfirmDialog() { @@ -60,7 +58,6 @@ public class ConfirmDialog extends AlertActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPackage = getCallingPackage(); - mCm = getSystemService(ConnectivityManager.class); mVm = getSystemService(VpnManager.class); if (mVm.prepareVpn(mPackage, null, UserHandle.myUserId())) { @@ -72,7 +69,7 @@ public class ConfirmDialog extends AlertActivity finish(); return; } - final String alwaysOnVpnPackage = mCm.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()); + final String alwaysOnVpnPackage = mVm.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()); // Can't prepare new vpn app when another vpn is always-on if (alwaysOnVpnPackage != null && !alwaysOnVpnPackage.equals(mPackage)) { finish(); diff --git a/packages/overlays/AccentColorAmethystOverlay/Android.bp b/packages/overlays/AccentColorAmethystOverlay/Android.bp index 7519b12dd2d9..186d770c09a5 100644 --- a/packages/overlays/AccentColorAmethystOverlay/Android.bp +++ b/packages/overlays/AccentColorAmethystOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorAmethystOverlay", theme: "AccentColorAmethyst", diff --git a/packages/overlays/AccentColorAquamarineOverlay/Android.bp b/packages/overlays/AccentColorAquamarineOverlay/Android.bp index 4469b36cfbc5..7fd64f374522 100644 --- a/packages/overlays/AccentColorAquamarineOverlay/Android.bp +++ b/packages/overlays/AccentColorAquamarineOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorAquamarineOverlay", theme: "AccentColorAquamarine", diff --git a/packages/overlays/AccentColorBlackOverlay/Android.bp b/packages/overlays/AccentColorBlackOverlay/Android.bp index bfee26ea52dd..ac923ebd7cd9 100644 --- a/packages/overlays/AccentColorBlackOverlay/Android.bp +++ b/packages/overlays/AccentColorBlackOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorBlackOverlay", theme: "AccentColorBlack", diff --git a/packages/overlays/AccentColorCarbonOverlay/Android.bp b/packages/overlays/AccentColorCarbonOverlay/Android.bp index 47f66dd9ba62..f4f1b8b50a1e 100644 --- a/packages/overlays/AccentColorCarbonOverlay/Android.bp +++ b/packages/overlays/AccentColorCarbonOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorCarbonOverlay", theme: "AccentColorCarbon", diff --git a/packages/overlays/AccentColorCinnamonOverlay/Android.bp b/packages/overlays/AccentColorCinnamonOverlay/Android.bp index 8250315256b8..53899bfefd98 100644 --- a/packages/overlays/AccentColorCinnamonOverlay/Android.bp +++ b/packages/overlays/AccentColorCinnamonOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorCinnamonOverlay", theme: "AccentColorCinnamon", diff --git a/packages/overlays/AccentColorGreenOverlay/Android.bp b/packages/overlays/AccentColorGreenOverlay/Android.bp index 15b50c76aa38..5b1f7447a7ca 100644 --- a/packages/overlays/AccentColorGreenOverlay/Android.bp +++ b/packages/overlays/AccentColorGreenOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorGreenOverlay", theme: "AccentColorGreen", diff --git a/packages/overlays/AccentColorOceanOverlay/Android.bp b/packages/overlays/AccentColorOceanOverlay/Android.bp index 6ad63bc92816..a85883044dc2 100644 --- a/packages/overlays/AccentColorOceanOverlay/Android.bp +++ b/packages/overlays/AccentColorOceanOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorOceanOverlay", theme: "AccentColorOcean", diff --git a/packages/overlays/AccentColorOrchidOverlay/Android.bp b/packages/overlays/AccentColorOrchidOverlay/Android.bp index b66933397e12..31ed30921664 100644 --- a/packages/overlays/AccentColorOrchidOverlay/Android.bp +++ b/packages/overlays/AccentColorOrchidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorOrchidOverlay", theme: "AccentColorOrchid", diff --git a/packages/overlays/AccentColorPaletteOverlay/Android.bp b/packages/overlays/AccentColorPaletteOverlay/Android.bp index eeefd1623a49..a6cc1dec37dd 100644 --- a/packages/overlays/AccentColorPaletteOverlay/Android.bp +++ b/packages/overlays/AccentColorPaletteOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorPaletteOverlay", theme: "AccentColorPalette", diff --git a/packages/overlays/AccentColorPurpleOverlay/Android.bp b/packages/overlays/AccentColorPurpleOverlay/Android.bp index ead95df18d9e..80e0ab1159b7 100644 --- a/packages/overlays/AccentColorPurpleOverlay/Android.bp +++ b/packages/overlays/AccentColorPurpleOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorPurpleOverlay", theme: "AccentColorPurple", diff --git a/packages/overlays/AccentColorSandOverlay/Android.bp b/packages/overlays/AccentColorSandOverlay/Android.bp index f70578a1ec38..771abca4c3f6 100644 --- a/packages/overlays/AccentColorSandOverlay/Android.bp +++ b/packages/overlays/AccentColorSandOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorSandOverlay", theme: "AccentColorSand", diff --git a/packages/overlays/AccentColorSpaceOverlay/Android.bp b/packages/overlays/AccentColorSpaceOverlay/Android.bp index 1d713df3cfdd..8e4abacf1ef2 100644 --- a/packages/overlays/AccentColorSpaceOverlay/Android.bp +++ b/packages/overlays/AccentColorSpaceOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorSpaceOverlay", theme: "AccentColorSpace", diff --git a/packages/overlays/AccentColorTangerineOverlay/Android.bp b/packages/overlays/AccentColorTangerineOverlay/Android.bp index d3b1e54fe823..75c708ec9fe7 100644 --- a/packages/overlays/AccentColorTangerineOverlay/Android.bp +++ b/packages/overlays/AccentColorTangerineOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "AccentColorTangerineOverlay", theme: "AccentColorTangerine", diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.bp index b8def98791be..8e03809cadf0 100644 --- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationCornerOverlay", theme: "DisplayCutoutEmulationCorner", diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.bp index b64ddfd82c05..afa5b649a432 100644 --- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationDoubleOverlay", theme: "DisplayCutoutEmulationDouble", diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.bp index 86cfebfbd0db..eae907db00ff 100644 --- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationHoleOverlay", theme: "DisplayCutoutEmulationHole", diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.bp index 28ad9db119cb..25bc676bb027 100644 --- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationNarrowOverlay", theme: "DisplayCutoutEmulationNarrow", diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.bp index cd00386658ed..2828612254a3 100644 --- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationTallOverlay", theme: "DisplayCutoutEmulationTall", diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.bp index d5fe683d5e55..66be777649ed 100644 --- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationWaterfallOverlay", theme: "DisplayCutoutEmulationWaterfall", diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.bp b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.bp index 0157ec48ee3a..e71cefe7643e 100644 --- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.bp +++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "DisplayCutoutEmulationWideOverlay", theme: "DisplayCutoutEmulationWide", diff --git a/packages/overlays/FontNotoSerifSourceOverlay/Android.bp b/packages/overlays/FontNotoSerifSourceOverlay/Android.bp index 7fd145b57934..231295b41a27 100644 --- a/packages/overlays/FontNotoSerifSourceOverlay/Android.bp +++ b/packages/overlays/FontNotoSerifSourceOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "FontNotoSerifSourceOverlay", theme: "FontNotoSerifSource", diff --git a/packages/overlays/IconPackCircularAndroidOverlay/Android.bp b/packages/overlays/IconPackCircularAndroidOverlay/Android.bp index cd5829aae3b6..70403588da33 100644 --- a/packages/overlays/IconPackCircularAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackCircularAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackCircularAndroidOverlay", theme: "IconPackCircularAndroid", diff --git a/packages/overlays/IconPackCircularLauncherOverlay/Android.bp b/packages/overlays/IconPackCircularLauncherOverlay/Android.bp index 5f2491d3cd66..4f8b6637a2b5 100644 --- a/packages/overlays/IconPackCircularLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackCircularLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackCircularLauncherOverlay", theme: "IconPackCircularLauncher", diff --git a/packages/overlays/IconPackCircularSettingsOverlay/Android.bp b/packages/overlays/IconPackCircularSettingsOverlay/Android.bp index d7bc6573e986..93220c87dcf9 100644 --- a/packages/overlays/IconPackCircularSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackCircularSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackCircularSettingsOverlay", theme: "IconPackCircularSettings", diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/Android.bp b/packages/overlays/IconPackCircularSystemUIOverlay/Android.bp index 73b8cd8e7d5f..4eaa4205fe96 100644 --- a/packages/overlays/IconPackCircularSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackCircularSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackCircularSystemUIOverlay", theme: "IconPackCircularSystemUI", diff --git a/packages/overlays/IconPackCircularThemePickerOverlay/Android.bp b/packages/overlays/IconPackCircularThemePickerOverlay/Android.bp index 50639322607e..5105b7931922 100644 --- a/packages/overlays/IconPackCircularThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackCircularThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackCircularThemePickerOverlay", theme: "IconPackCircularThemePicker", diff --git a/packages/overlays/IconPackFilledAndroidOverlay/Android.bp b/packages/overlays/IconPackFilledAndroidOverlay/Android.bp index 83f365678caf..3c4025d6026c 100644 --- a/packages/overlays/IconPackFilledAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackFilledAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackFilledAndroidOverlay", theme: "IconPackFilledAndroid", diff --git a/packages/overlays/IconPackFilledLauncherOverlay/Android.bp b/packages/overlays/IconPackFilledLauncherOverlay/Android.bp index 6ca256638a03..3c5078ce5933 100644 --- a/packages/overlays/IconPackFilledLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackFilledLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackFilledLauncherOverlay", theme: "IconPackFilledLauncher", diff --git a/packages/overlays/IconPackFilledSettingsOverlay/Android.bp b/packages/overlays/IconPackFilledSettingsOverlay/Android.bp index 8551bd54d588..b5148c23e053 100644 --- a/packages/overlays/IconPackFilledSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackFilledSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackFilledSettingsOverlay", theme: "IconPackFilledSettings", diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/Android.bp b/packages/overlays/IconPackFilledSystemUIOverlay/Android.bp index 684deb453d74..eb040a5a132d 100644 --- a/packages/overlays/IconPackFilledSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackFilledSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackFilledSystemUIOverlay", theme: "IconPackFilledSystemUI", diff --git a/packages/overlays/IconPackFilledThemePickerOverlay/Android.bp b/packages/overlays/IconPackFilledThemePickerOverlay/Android.bp index dae378f67774..bee48089109b 100644 --- a/packages/overlays/IconPackFilledThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackFilledThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackFilledThemePickerOverlay", theme: "IconPackFilledThemePicker", diff --git a/packages/overlays/IconPackKaiAndroidOverlay/Android.bp b/packages/overlays/IconPackKaiAndroidOverlay/Android.bp index 4161e252c312..ee588c1f1c55 100644 --- a/packages/overlays/IconPackKaiAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackKaiAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackKaiAndroidOverlay", theme: "IconPackKaiAndroid", diff --git a/packages/overlays/IconPackKaiLauncherOverlay/Android.bp b/packages/overlays/IconPackKaiLauncherOverlay/Android.bp index 4bf8f11a8475..dcdad7aaed4e 100644 --- a/packages/overlays/IconPackKaiLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackKaiLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackKaiLauncherOverlay", theme: "IconPackKaiLauncher", diff --git a/packages/overlays/IconPackKaiSettingsOverlay/Android.bp b/packages/overlays/IconPackKaiSettingsOverlay/Android.bp index c43f9e58a0c9..974bb540f4e7 100644 --- a/packages/overlays/IconPackKaiSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackKaiSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackKaiSettingsOverlay", theme: "IconPackKaiSettings", diff --git a/packages/overlays/IconPackKaiSystemUIOverlay/Android.bp b/packages/overlays/IconPackKaiSystemUIOverlay/Android.bp index 22a8c2843956..b04ca6132c6d 100644 --- a/packages/overlays/IconPackKaiSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackKaiSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackKaiSystemUIOverlay", theme: "IconPackKaiSystemUI", diff --git a/packages/overlays/IconPackKaiThemePickerOverlay/Android.bp b/packages/overlays/IconPackKaiThemePickerOverlay/Android.bp index 85eb1c46272f..875cd1d44d92 100644 --- a/packages/overlays/IconPackKaiThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackKaiThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackKaiThemePickerOverlay", theme: "IconPackKaiThemePicker", diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/Android.bp b/packages/overlays/IconPackRoundedAndroidOverlay/Android.bp index 948f015cabec..cb7b01361da3 100644 --- a/packages/overlays/IconPackRoundedAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackRoundedAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackRoundedAndroidOverlay", theme: "IconPackRoundedAndroid", diff --git a/packages/overlays/IconPackRoundedLauncherOverlay/Android.bp b/packages/overlays/IconPackRoundedLauncherOverlay/Android.bp index 5fbe6352f889..8ab6d957720e 100644 --- a/packages/overlays/IconPackRoundedLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackRoundedLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackRoundedLauncherOverlay", theme: "IconPackRoundedLauncher", diff --git a/packages/overlays/IconPackRoundedSettingsOverlay/Android.bp b/packages/overlays/IconPackRoundedSettingsOverlay/Android.bp index b1a07137eaa2..ee2f98a96864 100644 --- a/packages/overlays/IconPackRoundedSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackRoundedSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackRoundedSettingsOverlay", theme: "IconPackRoundedSettings", diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.bp b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.bp index 643299898169..ee0220a44943 100644 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackRoundedSystemUIOverlay", theme: "IconPackRoundedSystemUI", diff --git a/packages/overlays/IconPackRoundedThemePickerOverlay/Android.bp b/packages/overlays/IconPackRoundedThemePickerOverlay/Android.bp index 95e1d3a2ac6c..d74765c33607 100644 --- a/packages/overlays/IconPackRoundedThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackRoundedThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackRoundedThemePickerOverlay", theme: "IconPackRoundedTheme", diff --git a/packages/overlays/IconPackSamAndroidOverlay/Android.bp b/packages/overlays/IconPackSamAndroidOverlay/Android.bp index e8c33b10fb9e..2e9dc3472674 100644 --- a/packages/overlays/IconPackSamAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackSamAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackSamAndroidOverlay", theme: "IconPackSamAndroid", diff --git a/packages/overlays/IconPackSamLauncherOverlay/Android.bp b/packages/overlays/IconPackSamLauncherOverlay/Android.bp index a46964665862..aa0cf0077ab9 100644 --- a/packages/overlays/IconPackSamLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackSamLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackSamLauncherOverlay", theme: "IconPackSamLauncher", diff --git a/packages/overlays/IconPackSamSettingsOverlay/Android.bp b/packages/overlays/IconPackSamSettingsOverlay/Android.bp index bc1fa458ad9a..a62037f3d5c2 100644 --- a/packages/overlays/IconPackSamSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackSamSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackSamSettingsOverlay", theme: "IconPackSamSettings", diff --git a/packages/overlays/IconPackSamSystemUIOverlay/Android.bp b/packages/overlays/IconPackSamSystemUIOverlay/Android.bp index db77352f42b9..96ba7a096e6a 100644 --- a/packages/overlays/IconPackSamSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackSamSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackSamSystemUIOverlay", theme: "IconPackSamSystemUI", diff --git a/packages/overlays/IconPackSamThemePickerOverlay/Android.bp b/packages/overlays/IconPackSamThemePickerOverlay/Android.bp index c216868a2a5e..7376f03a2c7f 100644 --- a/packages/overlays/IconPackSamThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackSamThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackSamThemePickerOverlay", theme: "IconPackSamThemePicker", diff --git a/packages/overlays/IconPackVictorAndroidOverlay/Android.bp b/packages/overlays/IconPackVictorAndroidOverlay/Android.bp index de62af1c6287..ee7377863287 100644 --- a/packages/overlays/IconPackVictorAndroidOverlay/Android.bp +++ b/packages/overlays/IconPackVictorAndroidOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackVictorAndroidOverlay", theme: "IconPackVictorAndroid", diff --git a/packages/overlays/IconPackVictorLauncherOverlay/Android.bp b/packages/overlays/IconPackVictorLauncherOverlay/Android.bp index fc4c3606de8e..a0cd45a81e20 100644 --- a/packages/overlays/IconPackVictorLauncherOverlay/Android.bp +++ b/packages/overlays/IconPackVictorLauncherOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackVictorLauncherOverlay", theme: "IconPackVictorLauncher", diff --git a/packages/overlays/IconPackVictorSettingsOverlay/Android.bp b/packages/overlays/IconPackVictorSettingsOverlay/Android.bp index 046bb3d25a12..7807c6bcccc8 100644 --- a/packages/overlays/IconPackVictorSettingsOverlay/Android.bp +++ b/packages/overlays/IconPackVictorSettingsOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackVictorSettingsOverlay", theme: "IconPackVictorSettings", diff --git a/packages/overlays/IconPackVictorSystemUIOverlay/Android.bp b/packages/overlays/IconPackVictorSystemUIOverlay/Android.bp index b8a9e77ebf7d..2deb6cd73ba1 100644 --- a/packages/overlays/IconPackVictorSystemUIOverlay/Android.bp +++ b/packages/overlays/IconPackVictorSystemUIOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackVictorSystemUIOverlay", theme: "IconPackVictorSystemUI", diff --git a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp index 6f0144e9e351..a18ebb3eaea5 100644 --- a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp +++ b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconPackVictorThemePickerOverlay", theme: "IconPackVictorThemePicker", diff --git a/packages/overlays/IconShapeHeartOverlay/Android.bp b/packages/overlays/IconShapeHeartOverlay/Android.bp index ec55712f2309..1da8f4ff7fb3 100644 --- a/packages/overlays/IconShapeHeartOverlay/Android.bp +++ b/packages/overlays/IconShapeHeartOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeHeartOverlay", theme: "IconShapeHeart", diff --git a/packages/overlays/IconShapePebbleOverlay/Android.bp b/packages/overlays/IconShapePebbleOverlay/Android.bp index 7dc4fde140c8..fa2a5bb825f3 100644 --- a/packages/overlays/IconShapePebbleOverlay/Android.bp +++ b/packages/overlays/IconShapePebbleOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapePebbleOverlay", theme: "IconShapePebble", diff --git a/packages/overlays/IconShapeRoundedRectOverlay/Android.bp b/packages/overlays/IconShapeRoundedRectOverlay/Android.bp index b8b85314a211..5052d08f1edf 100644 --- a/packages/overlays/IconShapeRoundedRectOverlay/Android.bp +++ b/packages/overlays/IconShapeRoundedRectOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeRoundedRectOverlay", theme: "IconShapeRoundedRect", diff --git a/packages/overlays/IconShapeSquareOverlay/Android.bp b/packages/overlays/IconShapeSquareOverlay/Android.bp index fdeffeee25d2..1176abddd71f 100644 --- a/packages/overlays/IconShapeSquareOverlay/Android.bp +++ b/packages/overlays/IconShapeSquareOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeSquareOverlay", theme: "IconShapeSquare", diff --git a/packages/overlays/IconShapeSquircleOverlay/Android.bp b/packages/overlays/IconShapeSquircleOverlay/Android.bp index 468f0f7ee2c7..8c219f340040 100644 --- a/packages/overlays/IconShapeSquircleOverlay/Android.bp +++ b/packages/overlays/IconShapeSquircleOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeSquircleOverlay", theme: "IconShapeSquircle", diff --git a/packages/overlays/IconShapeTaperedRectOverlay/Android.bp b/packages/overlays/IconShapeTaperedRectOverlay/Android.bp index 1e48cd154317..78855e8daba2 100644 --- a/packages/overlays/IconShapeTaperedRectOverlay/Android.bp +++ b/packages/overlays/IconShapeTaperedRectOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeTaperedRectOverlay", theme: "IconShapeTaperedRect", diff --git a/packages/overlays/IconShapeTeardropOverlay/Android.bp b/packages/overlays/IconShapeTeardropOverlay/Android.bp index 017d58ec4e77..dd36f4ffc995 100644 --- a/packages/overlays/IconShapeTeardropOverlay/Android.bp +++ b/packages/overlays/IconShapeTeardropOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeTeardropOverlay", theme: "IconShapeTeardrop", diff --git a/packages/overlays/IconShapeVesselOverlay/Android.bp b/packages/overlays/IconShapeVesselOverlay/Android.bp index ba3b30954e7f..2e7f8bc6cf66 100644 --- a/packages/overlays/IconShapeVesselOverlay/Android.bp +++ b/packages/overlays/IconShapeVesselOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "IconShapeVesselOverlay", theme: "IconShapeVessel", diff --git a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.bp b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.bp index e4fcce15250f..35f671bab875 100644 --- a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.bp +++ b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarMode2ButtonOverlay", theme: "NavigationBarMode2Button", diff --git a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.bp b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.bp index 9b6c9988b9bb..fe9cc81f0d52 100644 --- a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.bp +++ b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarMode3ButtonOverlay", theme: "NavigationBarMode3Button", diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlay/Android.bp index ba290459a279..791f4205481f 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/Android.bp +++ b/packages/overlays/NavigationBarModeGesturalOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarModeGesturalOverlay", theme: "NavigationBarModeGestural", diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp index 0c688a988533..28f9f3341307 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp +++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarModeGesturalOverlayExtraWideBack", theme: "NavigationBarModeGesturalExtraWideBack", diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp index 85d514f7fbac..f8a56030e0e4 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp +++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarModeGesturalOverlayNarrowBack", theme: "NavigationBarModeGesturalNarrowBack", diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp index 84be626eefec..60ee6d540dc8 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp +++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "NavigationBarModeGesturalOverlayWideBack", theme: "NavigationBarModeGesturalWideBack", diff --git a/packages/overlays/OneHandedModeGesturalOverlay/Android.bp b/packages/overlays/OneHandedModeGesturalOverlay/Android.bp index 9c9d0ef380a7..468069dd8334 100644 --- a/packages/overlays/OneHandedModeGesturalOverlay/Android.bp +++ b/packages/overlays/OneHandedModeGesturalOverlay/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + runtime_resource_overlay { name: "OneHandedModeGesturalOverlay", theme: "OneHandedModeGestural", diff --git a/packages/services/CameraExtensionsProxy/Android.bp b/packages/services/CameraExtensionsProxy/Android.bp index e2e4af2a35f4..ea0703e2bc24 100644 --- a/packages/services/CameraExtensionsProxy/Android.bp +++ b/packages/services/CameraExtensionsProxy/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_app { name: "CameraExtensionsProxy", srcs: ["src/**/*.java"], diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index ea1473ea3db7..c63c2e1a257d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -777,6 +777,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // performs the current profile parent resolution. final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + + if (Binder.getCallingPid() == OWN_PROCESS_ID) { + return new ArrayList<>(getUserStateLocked(resolvedUserId).mInstalledServices); + } return getUserStateLocked(resolvedUserId).mInstalledServices; } } diff --git a/services/api/Android.bp b/services/api/Android.bp new file mode 100644 index 000000000000..b8ca5488c5cd --- /dev/null +++ b/services/api/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_visibility: ["//visibility:private"], +} + +filegroup { + name: "non-updatable-system-server-current.txt", + srcs: ["non-updatable-current.txt"], + visibility: ["//frameworks/base/api"], +} + +filegroup { + name: "non-updatable-system-server-removed.txt", + srcs: ["non-updatable-removed.txt"], + visibility: ["//frameworks/base/api"], +}
\ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 809304bb24ae..50ad6617b1fe 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -137,7 +137,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -181,9 +180,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } switch (action) { - case Intent.ACTION_CONFIGURATION_CHANGED: - onConfigurationChanged(); - break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: synchronized (mLock) { @@ -243,8 +239,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private Handler mSaveStateHandler; private Handler mCallbackHandler; - private Locale mLocale; - private final SparseIntArray mNextAppWidgetIds = new SparseIntArray(); private boolean mSafeMode; @@ -290,13 +284,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void registerBroadcastReceiver() { - // Register for configuration changes so we can update the names - // of the widgets when the locale changes. - IntentFilter configFilter = new IntentFilter(); - configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, - configFilter, null, null); - // Register for broadcasts about package install, etc., so we can // update the provider list. IntentFilter packageFilter = new IntentFilter(); @@ -338,62 +325,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSafeMode = safeMode; } - private void onConfigurationChanged() { - if (DEBUG) { - Slog.i(TAG, "onConfigurationChanged()"); - } - - Locale revised = Locale.getDefault(); - if (revised == null || mLocale == null || !revised.equals(mLocale)) { - mLocale = revised; - - synchronized (mLock) { - SparseIntArray changedGroups = null; - - // Note: updateProvidersForPackageLocked() may remove providers, so we must copy the - // list of installed providers and skip providers that we don't need to update. - // Also note that remove the provider does not clear the Provider component data. - ArrayList<Provider> installedProviders = new ArrayList<>(mProviders); - HashSet<ProviderId> removedProviders = new HashSet<>(); - - int N = installedProviders.size(); - for (int i = N - 1; i >= 0; i--) { - Provider provider = installedProviders.get(i); - - final int userId = provider.getUserId(); - if (!mUserManager.isUserUnlockingOrUnlocked(userId) || - isProfileWithLockedParent(userId)) { - continue; - } - ensureGroupStateLoadedLocked(userId); - - if (!removedProviders.contains(provider.id)) { - final boolean changed = updateProvidersForPackageLocked( - provider.id.componentName.getPackageName(), - provider.getUserId(), removedProviders); - - if (changed) { - if (changedGroups == null) { - changedGroups = new SparseIntArray(); - } - final int groupId = mSecurityPolicy.getGroupParent( - provider.getUserId()); - changedGroups.put(groupId, groupId); - } - } - } - - if (changedGroups != null) { - final int groupCount = changedGroups.size(); - for (int i = 0; i < groupCount; i++) { - final int groupId = changedGroups.get(i); - saveGroupStateAsync(groupId); - } - } - } - } - } - private void onPackageBroadcastReceived(Intent intent, int userId) { final String action = intent.getAction(); boolean added = false; @@ -2673,7 +2604,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN); info.widgetFeatures = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_widgetFeatures, 0); - info.descriptionResource = sa.getResourceId( + info.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AppWidgetProviderInfo_description, Resources.ID_NULL); sa.recycle(); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 6c30999f63a4..38275f7cd348 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1245,10 +1245,9 @@ public class BackupManagerService extends IBackupManager.Stub { @Override public IRestoreSession beginRestoreSessionForUser( - int userId, String packageName, String transportID, - @OperationType int operationType) throws RemoteException { + int userId, String packageName, String transportID) throws RemoteException { return isUserReadyForBackup(userId) - ? beginRestoreSession(userId, packageName, transportID, operationType) : null; + ? beginRestoreSession(userId, packageName, transportID) : null; } /** @@ -1257,15 +1256,13 @@ public class BackupManagerService extends IBackupManager.Stub { */ @Nullable public IRestoreSession beginRestoreSession( - @UserIdInt int userId, String packageName, String transportName, - @OperationType int operationType) { + @UserIdInt int userId, String packageName, String transportName) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); return userBackupManagerService == null ? null - : userBackupManagerService.beginRestoreSession(packageName, transportName, - operationType); + : userBackupManagerService.beginRestoreSession(packageName, transportName); } @Override @@ -1350,15 +1347,15 @@ public class BackupManagerService extends IBackupManager.Stub { if (!isUserReadyForBackup(userId)) { return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } - return requestBackup(userId, packages, observer, monitor, flags, OperationType.BACKUP); + return requestBackup(userId, packages, observer, monitor, flags); } @Override public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags, @OperationType int operationType) + IBackupManagerMonitor monitor, int flags) throws RemoteException { return requestBackup(binderGetCallingUserId(), packages, - observer, monitor, flags, operationType); + observer, monitor, flags); } /** @@ -1370,15 +1367,13 @@ public class BackupManagerService extends IBackupManager.Stub { String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, - int flags, - @OperationType int operationType) { + int flags) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); return userBackupManagerService == null ? BackupManager.ERROR_BACKUP_NOT_ALLOWED - : userBackupManagerService.requestBackup(packages, observer, monitor, flags, - operationType); + : userBackupManagerService.requestBackup(packages, observer, monitor, flags); } @Override diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 136cd22fad83..9ee0159e903a 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -96,6 +96,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -127,6 +128,7 @@ import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.backup.utils.BackupEligibilityRules; import com.android.server.backup.utils.BackupManagerMonitorUtils; @@ -1860,19 +1862,10 @@ public class UserBackupManagerService { /** * Requests a backup for the inputted {@code packages} with a specified {@link - * IBackupManagerMonitor}. - */ - public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) { - return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP); - } - - /** - * Requests a backup for the inputted {@code packages} with a specified {@link * IBackupManagerMonitor} and {@link OperationType}. */ public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags, @OperationType int operationType) { + IBackupManagerMonitor monitor, int flags) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup"); if (packages == null || packages.length < 1) { @@ -1903,13 +1896,16 @@ public class UserBackupManagerService { final TransportClient transportClient; final String transportDirName; + int operationType; try { transportDirName = mTransportManager.getTransportDirName( mTransportManager.getCurrentTransportName()); transportClient = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); - } catch (TransportNotRegisteredException e) { + operationType = getOperationTypeFromTransport(transportClient); + } catch (TransportNotRegisteredException | TransportNotAvailableException + | RemoteException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -4024,15 +4020,13 @@ public class UserBackupManagerService { } /** Hand off a restore session. */ - public IRestoreSession beginRestoreSession(String packageName, String transport, - @OperationType int operationType) { + public IRestoreSession beginRestoreSession(String packageName, String transport) { if (DEBUG) { Slog.v( TAG, addUserIdToLogMessage( mUserId, - "beginRestoreSession: pkg=" + packageName + " transport=" + transport - + "operationType=" + operationType)); + "beginRestoreSession: pkg=" + packageName + " transport=" + transport)); } boolean needPermission = true; @@ -4073,6 +4067,17 @@ public class UserBackupManagerService { } } + int operationType; + try { + operationType = getOperationTypeFromTransport( + mTransportManager.getTransportClientOrThrow(transport, /* caller */ + "BMS.beginRestoreSession")); + } catch (TransportNotAvailableException | TransportNotRegisteredException + | RemoteException e) { + Slog.w(TAG, "Failed to get operation type from transport: " + e); + return null; + } + synchronized (this) { if (mActiveRestoreSession != null) { Slog.i( @@ -4356,6 +4361,34 @@ public class UserBackupManagerService { } } + @VisibleForTesting + @OperationType int getOperationTypeFromTransport(TransportClient transportClient) + throws TransportNotAvailableException, RemoteException { + if (!shouldUseNewBackupEligibilityRules()) { + // Return the default to stick to the legacy behaviour. + return OperationType.BACKUP; + } + + long oldCallingId = Binder.clearCallingIdentity(); + try { + IBackupTransport transport = transportClient.connectOrThrow( + /* caller */ "BMS.getOperationTypeFromTransport"); + if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) { + return OperationType.MIGRATION; + } else { + return OperationType.BACKUP; + } + } finally { + Binder.restoreCallingIdentity(oldCallingId); + } + } + + @VisibleForTesting + boolean shouldUseNewBackupEligibilityRules() { + return FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES); + } + private static String addUserIdToLogMessage(int userId, String message) { return "[UserID:" + userId + "] " + message; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 76c8d3001158..21cae453d702 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -142,18 +142,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; -//TODO onStop schedule unbind in 5 seconds -//TODO make sure APIs are only callable from currently focused app -//TODO schedule stopScan on activity destroy(except if configuration change) -//TODO on associate called again after configuration change -> replace old callback with new -//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example) /** @hide */ @SuppressLint("LongLogTag") public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient { private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, - ".DeviceDiscoveryService"); + ".CompanionDeviceDiscoveryService"); private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000; private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000; @@ -747,6 +742,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } } + + if (association.isNotifyOnDeviceNearby()) { + ServiceConnector<ICompanionDeviceService> serviceConnector = + mDeviceListenerServiceConnectors.forUser(association.getUserId()) + .get(association.getPackageName()); + if (serviceConnector != null) { + serviceConnector.unbind(); + } + } } private void updateSpecialAccessPermissionForAssociatedPackage(Association association) { @@ -1422,7 +1426,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } @Override - public void onDeviceDisconnected(BluetoothDevice device) { + public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) { + Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") " + + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason)); CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 9d86f4eaa520..542d527177a1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -44,6 +44,7 @@ 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; @@ -91,7 +92,6 @@ import android.net.IConnectivityManager; import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkActivityListener; -import android.net.INetworkManagementEventObserver; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; @@ -188,14 +188,17 @@ 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.util.ArrayUtils; import com.android.internal.util.AsyncChannel; +import com.android.internal.util.BitUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.modules.utils.BasicShellCommandHandler; +import com.android.net.module.util.BaseNetdUnsolicitedEventListener; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; +import com.android.net.module.util.PermissionUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.AutodestructReference; import com.android.server.connectivity.DataConnectionStats; @@ -212,7 +215,6 @@ 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.net.BaseNetworkObserver; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -330,6 +332,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkStatsService mStatsService; private NetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; + private final NetdCallback mNetdCallback; /** * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple @@ -1202,6 +1205,13 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNMS, mNetd); + mNetdCallback = new NetdCallback(); + try { + mNetd.registerUnsolicitedEventListener(mNetdCallback); + } catch (RemoteException | ServiceSpecificException e) { + loge("Error registering event listener :" + e); + } + mSettingsObserver = new SettingsObserver(mContext, mHandler); registerSettingsCallbacks(); @@ -1239,6 +1249,7 @@ 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; @@ -1253,6 +1264,7 @@ 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); @@ -1510,7 +1522,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); return getActiveNetworkForUidInternal(uid, ignoreBlocked); } @@ -1533,7 +1545,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); final NetworkState state = getUnfilteredActiveNetworkState(uid); filterNetworkStateForUid(state, uid, ignoreBlocked); return state.networkInfo; @@ -1877,7 +1889,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkState[] getAllNetworkState() { // This contains IMSI details, so make sure the caller is privileged. - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); final ArrayList<NetworkState> result = new ArrayList<>(); for (Network network : getAllNetworks()) { @@ -2301,7 +2313,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Public because it's used by mLockdownTracker. public void sendConnectedBroadcast(NetworkInfo info) { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); sendGeneralBroadcast(info, CONNECTIVITY_ACTION); } @@ -2565,13 +2577,13 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!checkDumpPermission(mContext, TAG, pw)) return; if (asProto) return; - if (ArrayUtils.contains(args, DIAG_ARG)) { + if (CollectionUtils.contains(args, DIAG_ARG)) { dumpNetworkDiagnostics(pw); return; - } else if (ArrayUtils.contains(args, NETWORK_ARG)) { + } else if (CollectionUtils.contains(args, NETWORK_ARG)) { dumpNetworks(pw); return; - } else if (ArrayUtils.contains(args, REQUEST_ARG)) { + } else if (CollectionUtils.contains(args, REQUEST_ARG)) { dumpNetworkRequests(pw); return; } @@ -2642,7 +2654,7 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); - if (ArrayUtils.contains(args, SHORT_ARG) == false) { + if (!CollectionUtils.contains(args, SHORT_ARG)) { pw.println(); pw.println("mNetworkRequestInfoLogs (most recent first):"); pw.increaseIndent(); @@ -4684,7 +4696,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void setGlobalProxy(final ProxyInfo proxyProperties) { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); mProxyTracker.setGlobalProxy(proxyProperties); } @@ -4809,7 +4821,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - if (ArrayUtils.isEmpty(underlyingNetworks)) return null; + if (CollectionUtils.isEmpty(underlyingNetworks)) return null; List<String> interfaces = new ArrayList<>(); for (Network network : underlyingNetworks) { @@ -4853,7 +4865,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!nai.supportsUnderlyingNetworks()) return false; final Network[] underlying = underlyingNetworksOrDefault( nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks); - return ArrayUtils.contains(underlying, network); + return CollectionUtils.contains(underlying, network); } /** @@ -4886,7 +4898,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) { - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS, encodeBool(requireVpn), 0 /* arg2 */, ranges)); } @@ -5317,8 +5329,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } - // TODO: use NetworkStackUtils.convertToIntArray after moving it - return ArrayUtils.convertToIntArray(new ArrayList<>(thresholds)); + return CollectionUtils.toIntArray(new ArrayList<>(thresholds)); } private void updateSignalStrengthThresholds( @@ -6437,7 +6448,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) { underlyingNetworks = underlyingNetworksOrDefault( agentCaps.getOwnerUid(), underlyingNetworks); - int[] transportTypes = agentCaps.getTransportTypes(); + long transportTypes = BitUtils.packBits(agentCaps.getTransportTypes()); int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; // metered if any underlying is metered, or originally declared metered by the agent. @@ -6456,7 +6467,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities underlyingCaps = underlying.networkCapabilities; hadUnderlyingNetworks = true; for (int underlyingType : underlyingCaps.getTransportTypes()) { - transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType); + transportTypes |= 1L << underlyingType; } // Merge capabilities of this underlying network. For bandwidth, assume the @@ -6487,7 +6498,7 @@ public class ConnectivityService extends IConnectivityManager.Stub suspended = false; } - newNc.setTransportTypes(transportTypes); + newNc.setTransportTypes(BitUtils.unpackBits(transportTypes)); newNc.setLinkDownstreamBandwidthKbps(downKbps); newNc.setLinkUpstreamBandwidthKbps(upKbps); newNc.setCapability(NET_CAPABILITY_NOT_METERED, !metered); @@ -8552,14 +8563,14 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo virtual : mNetworkAgentInfos) { if (virtual.supportsUnderlyingNetworks() && virtual.networkCapabilities.getOwnerUid() == callbackUid - && ArrayUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) { + && CollectionUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) { return true; } } // Administrator UIDs also contains the Owner UID final int[] administratorUids = nai.networkCapabilities.getAdministratorUids(); - return ArrayUtils.contains(administratorUids, callbackUid); + return CollectionUtils.contains(administratorUids, callbackUid); } @Override @@ -8648,6 +8659,14 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyDataStallSuspected(p, network.getNetId()); } + private class NetdCallback extends BaseNetdUnsolicitedEventListener { + @Override + public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, + long timestampNs, int uid) { + mNetworkActivityTracker.setAndReportNetworkActive(isActive, timerLabel, timestampNs); + } + } + private final LegacyNetworkActivityTracker mNetworkActivityTracker; /** @@ -8658,7 +8677,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int NO_UID = -1; private final Context mContext; private final INetd mNetd; - private final INetworkManagementService mNMS; private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners = new RemoteCallbackList<>(); // Indicate the current system default network activity is active or not. @@ -8681,41 +8699,27 @@ public class ConnectivityService extends IConnectivityManager.Stub LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler, @NonNull INetworkManagementService nms, @NonNull INetd netd) { mContext = context; - mNMS = nms; mNetd = netd; mHandler = handler; - try { - mNMS.registerObserver(mDataActivityObserver); - } catch (RemoteException e) { - loge("Error registering observer :" + e); - } - } - - // TODO: Migrate away the dependency with INetworkManagementEventObserver. - private final INetworkManagementEventObserver mDataActivityObserver = - new BaseNetworkObserver() { - @Override - public void interfaceClassDataActivityChanged(int transportType, boolean active, - long tsNanos, int uid) { - sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, - tsNanos); - synchronized (mActiveIdleTimers) { - mNetworkActive = active; - // If there are no idle timers, it means that system is not monitoring - // activity, so the system default network for those default network - // unspecified apps is always considered active. - // - // TODO: If the mActiveIdleTimers is empty, netd will actually not send - // any network activity change event. Whenever this event is received, - // the mActiveIdleTimers should be always not empty. The legacy behavior - // is no-op. Remove to refer to mNetworkActive only. - if (mNetworkActive || mActiveIdleTimers.isEmpty()) { - mHandler.sendMessage( - mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY)); - } - } - } - }; + } + + public void setAndReportNetworkActive(boolean active, int transportType, long tsNanos) { + sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos); + synchronized (mActiveIdleTimers) { + mNetworkActive = active; + // If there are no idle timers, it means that system is not monitoring + // activity, so the system default network for those default network + // unspecified apps is always considered active. + // + // TODO: If the mActiveIdleTimers is empty, netd will actually not send + // any network activity change event. Whenever this event is received, + // the mActiveIdleTimers should be always not empty. The legacy behavior + // is no-op. Remove to refer to mNetworkActive only. + if (mNetworkActive || mActiveIdleTimers.isEmpty()) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY)); + } + } + } // The network activity should only be updated from ConnectivityService handler thread // when mActiveIdleTimers lock is held. diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index 88ce2208adcb..e29e894a5cc0 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -28,6 +28,7 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.image.IDynamicSystemService; +import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.util.Slog; @@ -40,6 +41,7 @@ import java.io.File; */ public class DynamicSystemService extends IDynamicSystemService.Stub { private static final String TAG = "DynamicSystemService"; + private static final long MINIMUM_SD_MB = (30L << 10); private static final int GSID_ROUGH_TIMEOUT_MS = 8192; private static final String PATH_DEFAULT = "/data/gsi/"; private Context mContext; @@ -95,6 +97,13 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { if (!volume.isMountedWritable()) { continue; } + DiskInfo disk = volume.getDisk(); + long mega = disk.size >> 20; + Slog.i(TAG, volume.getPath() + ": " + mega + " MB"); + if (mega < MINIMUM_SD_MB) { + Slog.i(TAG, volume.getPath() + ": insufficient storage"); + continue; + } File sd_internal = volume.getInternalPathForUser(userId); if (sd_internal != null) { path = new File(sd_internal, dsuSlot).getPath(); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index b48bc900aa84..81d4b9da63c8 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -48,7 +48,6 @@ import android.net.TrafficStats; import android.net.util.NetdService; import android.os.Binder; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -64,6 +63,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.net.module.util.NetdUtils; import libcore.io.IoUtils; @@ -117,9 +117,6 @@ public class IpSecService extends IIpSecService.Stub { /* Binder context for this service */ private final Context mContext; - /* NetworkManager instance */ - private final INetworkManagementService mNetworkManager; - /** * The next non-repeating global ID for tracking resources between users, this service, and * kernel data structures. Accessing this variable is not thread safe, so it is only read or @@ -1014,13 +1011,13 @@ public class IpSecService extends IIpSecService.Stub { * * @param context Binder context for this service */ - private IpSecService(Context context, INetworkManagementService networkManager) { - this(context, networkManager, IpSecServiceConfiguration.GETSRVINSTANCE); + private IpSecService(Context context) { + this(context, IpSecServiceConfiguration.GETSRVINSTANCE); } - static IpSecService create(Context context, INetworkManagementService networkManager) + static IpSecService create(Context context) throws InterruptedException { - final IpSecService service = new IpSecService(context, networkManager); + final IpSecService service = new IpSecService(context); service.connectNativeNetdService(); return service; } @@ -1034,11 +1031,9 @@ public class IpSecService extends IIpSecService.Stub { /** @hide */ @VisibleForTesting - public IpSecService(Context context, INetworkManagementService networkManager, - IpSecServiceConfiguration config) { + public IpSecService(Context context, IpSecServiceConfiguration config) { this( context, - networkManager, config, (fd, uid) -> { try { @@ -1052,10 +1047,9 @@ public class IpSecService extends IIpSecService.Stub { /** @hide */ @VisibleForTesting - public IpSecService(Context context, INetworkManagementService networkManager, - IpSecServiceConfiguration config, UidFdTagger uidFdTagger) { + public IpSecService(Context context, IpSecServiceConfiguration config, + UidFdTagger uidFdTagger) { mContext = context; - mNetworkManager = Objects.requireNonNull(networkManager); mSrvConfig = config; mUidFdTagger = uidFdTagger; } @@ -1335,7 +1329,7 @@ public class IpSecService extends IIpSecService.Stub { netd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId); Binder.withCleanCallingIdentity(() -> { - mNetworkManager.setInterfaceUp(intfName); + NetdUtils.setInterfaceUp(netd, intfName); }); for (int selAddrFamily : ADDRESS_FAMILIES) { diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 84e429dc83e4..f2782f64995a 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -25,9 +25,9 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.os.UserHandle.USER_SYSTEM; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; @@ -406,12 +406,12 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void setSensorPrivacy(boolean enable) { + enforceManageSensorPrivacyPermission(); // Keep the state consistent between all users to make it a single global state forAllUsers(userId -> setSensorPrivacy(userId, enable)); } private void setSensorPrivacy(@UserIdInt int userId, boolean enable) { - enforceSensorPrivacyPermission(); synchronized (mLock) { mEnabled.put(userId, enable); persistSensorPrivacyStateLocked(); @@ -421,7 +421,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) { - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId, new SparseBooleanArray()); @@ -448,6 +448,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor, boolean enable) { + enforceManageSensorPrivacyPermission(); int parentId = mUserManagerInternal.getProfileParentId(userId); forAllUsers(userId2 -> { if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { @@ -460,21 +461,35 @@ public final class SensorPrivacyService extends SystemService { * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ - private void enforceSensorPrivacyPermission() { - if (mContext.checkCallingOrSelfPermission( - MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { - return; - } - throw new SecurityException( + private void enforceManageSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY, "Changing sensor privacy requires the following permission: " + MANAGE_SENSOR_PRIVACY); } /** + * Enforces the caller contains the necessary permission to observe changes to the sate of + * sensor privacy. + */ + private void enforceObserveSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY, + "Observing sensor privacy changes requires the following permission: " + + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY); + } + + private void enforcePermission(String permission, String message) { + if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return; + } + throw new SecurityException(message); + } + + /** * Returns whether sensor privacy is enabled. */ @Override public boolean isSensorPrivacyEnabled() { + enforceObserveSensorPrivacyPermission(); return isSensorPrivacyEnabled(USER_SYSTEM); } @@ -486,6 +501,7 @@ public final class SensorPrivacyService extends SystemService { @Override public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { + enforceObserveSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray states = mIndividualEnabled.get(userId); if (states == null) { @@ -703,6 +719,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void addSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -715,6 +732,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void addIndividualSensorPrivacyListener(int userId, int sensor, ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -726,6 +744,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void removeSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -735,6 +754,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void suppressIndividualSensorPrivacyReminders(int userId, String packageName, IBinder token, boolean suppress) { + enforceManageSensorPrivacyPermission(); Objects.requireNonNull(packageName); Objects.requireNonNull(token); @@ -886,13 +906,13 @@ public final class SensorPrivacyService extends SystemService { } /** - * Convert a string into a {@link SensorPrivacyManager.IndividualSensor id}. + * Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}. * * @param sensor The name to convert * * @return The id corresponding to the name */ - private @SensorPrivacyManager.IndividualSensor int sensorStrToId(@Nullable String sensor) { + private @SensorPrivacyManager.Sensors.Sensor int sensorStrToId(@Nullable String sensor) { if (sensor == null) { return UNKNOWN; } @@ -950,7 +970,7 @@ public final class SensorPrivacyService extends SystemService { return -1; } - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray individualEnabled = diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index c2d8fa24157a..8a2894c84cc4 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -45,14 +45,18 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.Immutable; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -87,26 +91,30 @@ public class ServiceWatcher implements ServiceConnection { /** Function to run on binder interface when first bound. */ public interface OnBindRunner { /** Called to run client code with the binder. */ - void run(IBinder binder, ComponentName service) throws RemoteException; + void run(IBinder binder, BoundService service) throws RemoteException; } /** * Information on the service ServiceWatcher has selected as the best option for binding. */ - private static final class ServiceInfo implements Comparable<ServiceInfo> { + @Immutable + public static final class BoundService implements Comparable<BoundService> { - public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null, - UserHandle.USER_NULL, false); + public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null, + false, null, -1); public final int version; - @Nullable public final ComponentName component; - @UserIdInt public final int userId; + @Nullable + public final ComponentName component; public final boolean serviceIsMultiuser; + public final int uid; + @Nullable + public final Bundle metadata; - ServiceInfo(ResolveInfo resolveInfo, int currentUserId) { + BoundService(ResolveInfo resolveInfo) { Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); - Bundle metadata = resolveInfo.serviceInfo.metaData; + metadata = resolveInfo.serviceInfo.metaData; if (metadata != null) { version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); @@ -116,16 +124,17 @@ public class ServiceWatcher implements ServiceConnection { } component = resolveInfo.serviceInfo.getComponentName(); - userId = serviceIsMultiuser ? UserHandle.USER_SYSTEM : currentUserId; + uid = resolveInfo.serviceInfo.applicationInfo.uid; } - private ServiceInfo(int version, @Nullable ComponentName component, int userId, - boolean serviceIsMultiuser) { + private BoundService(int version, @Nullable ComponentName component, + boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) { Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE); this.version = version; this.component = component; - this.userId = userId; this.serviceIsMultiuser = serviceIsMultiuser; + this.metadata = metadata; + this.uid = uid; } public @Nullable String getPackageName() { @@ -137,21 +146,21 @@ public class ServiceWatcher implements ServiceConnection { if (this == o) { return true; } - if (!(o instanceof ServiceInfo)) { + if (!(o instanceof BoundService)) { return false; } - ServiceInfo that = (ServiceInfo) o; - return version == that.version && userId == that.userId + BoundService that = (BoundService) o; + return version == that.version && uid == that.uid && Objects.equals(component, that.component); } @Override public int hashCode() { - return Objects.hash(version, component, userId); + return Objects.hash(version, component, uid); } @Override - public int compareTo(ServiceInfo that) { + public int compareTo(BoundService that) { // ServiceInfos with higher version numbers always win (having a version number > // MIN_VALUE implies having a non-null component). if version numbers are equal, a // non-null component wins over a null component. if the version numbers are equal and @@ -164,10 +173,11 @@ public class ServiceWatcher implements ServiceConnection { } else if (component != null && that.component == null) { ret = 1; } else { - if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) { + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) { ret = -1; - } else if (userId == UserHandle.USER_SYSTEM - && that.userId != UserHandle.USER_SYSTEM) { + } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) { ret = 1; } } @@ -180,7 +190,8 @@ public class ServiceWatcher implements ServiceConnection { if (component == null) { return "none"; } else { - return component.toShortString() + "@" + version + "[u" + userId + "]"; + return component.toShortString() + "@" + version + "[u" + + UserHandle.getUserId(uid) + "]"; } } } @@ -227,17 +238,23 @@ public class ServiceWatcher implements ServiceConnection { } }; - @Nullable private final OnBindRunner mOnBind; - @Nullable private final Runnable mOnUnbind; + // read/write from handler thread only + private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>(); + + @Nullable + private final OnBindRunner mOnBind; + + @Nullable + private final Runnable mOnUnbind; - // write from caller thread only, read anywhere - private volatile boolean mRegistered; + // read/write from handler thread only + private boolean mRegistered; // read/write from handler thread only private int mCurrentUserId; // write from handler thread only, read anywhere - private volatile ServiceInfo mTargetService; + private volatile BoundService mTargetService; private volatile IBinder mBinder; public ServiceWatcher(Context context, String action, @@ -274,7 +291,7 @@ public class ServiceWatcher implements ServiceConnection { mCurrentUserId = UserHandle.USER_NULL; - mTargetService = ServiceInfo.NONE; + mTargetService = BoundService.NONE; mBinder = null; } @@ -299,6 +316,11 @@ public class ServiceWatcher implements ServiceConnection { * Starts the process of determining the best matching service and maintaining a binding to it. */ public void register() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal, + ServiceWatcher.this)); + } + + private void registerInternal() { Preconditions.checkState(!mRegistered); mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler); @@ -309,6 +331,8 @@ public class ServiceWatcher implements ServiceConnection { mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler); + // TODO: This makes the behavior of the class unpredictable as the caller needs + // to know the internal impl detail that calling register would pick the current user. mCurrentUserId = ActivityManager.getCurrentUser(); mRegistered = true; @@ -320,6 +344,11 @@ public class ServiceWatcher implements ServiceConnection { * Stops the process of determining the best matching service and releases any binding. */ public void unregister() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal, + ServiceWatcher.this)); + } + + private void unregisterInternal() { Preconditions.checkState(mRegistered); mRegistered = false; @@ -333,7 +362,7 @@ public class ServiceWatcher implements ServiceConnection { private void onBestServiceChanged(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - ServiceInfo bestServiceInfo = ServiceInfo.NONE; + BoundService bestServiceInfo = BoundService.NONE; if (mRegistered) { List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( @@ -344,7 +373,7 @@ public class ServiceWatcher implements ServiceConnection { if (!mServiceCheckPredicate.test(resolveInfo)) { continue; } - ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId); + BoundService serviceInfo = new BoundService(resolveInfo); if (serviceInfo.compareTo(bestServiceInfo) > 0) { bestServiceInfo = serviceInfo; } @@ -356,21 +385,22 @@ public class ServiceWatcher implements ServiceConnection { } } - private void rebind(ServiceInfo newServiceInfo) { + private void rebind(BoundService newServiceInfo) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - if (!mTargetService.equals(ServiceInfo.NONE)) { + if (!mTargetService.equals(BoundService.NONE)) { if (D) { Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService); } mContext.unbindService(this); onServiceDisconnected(mTargetService.component); - mTargetService = ServiceInfo.NONE; + mPendingBinds.remove(mTargetService.component); + mTargetService = BoundService.NONE; } mTargetService = newServiceInfo; - if (mTargetService.equals(ServiceInfo.NONE)) { + if (mTargetService.equals(BoundService.NONE)) { return; } @@ -381,10 +411,12 @@ public class ServiceWatcher implements ServiceConnection { Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component); if (!mContext.bindServiceAsUser(bindIntent, this, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, - mHandler, UserHandle.of(mTargetService.userId))) { - mTargetService = ServiceInfo.NONE; + mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) { + mTargetService = BoundService.NONE; Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later"); mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS); + } else { + mPendingBinds.put(mTargetService.component, mTargetService); } } @@ -397,10 +429,15 @@ public class ServiceWatcher implements ServiceConnection { Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString()); } + final BoundService boundService = mPendingBinds.remove(component); + if (boundService == null) { + return; + } + mBinder = binder; if (mOnBind != null) { try { - mOnBind.run(binder, component); + mOnBind.run(binder, boundService); } catch (RuntimeException | RemoteException e) { // binders may propagate some specific non-RemoteExceptions from the other side // through the binder as well - we cannot allow those to crash the system server diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 2f9819997257..6be7f05f6cc6 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -51,6 +51,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AnrController; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; @@ -938,14 +939,29 @@ class StorageManagerService extends IStorageManager.Stub if (transcodeEnabled) { LocalServices.getService(ActivityManagerInternal.class) - .registerAnrController((packageName, uid) -> { - try { - return mStorageSessionController.getAnrDelayMillis(packageName, uid); - } catch (ExternalStorageServiceException e) { - Log.e(TAG, "Failed to get ANR delay for " + packageName, e); - return 0; - } - }); + .registerAnrController(new ExternalStorageServiceAnrController()); + } + } + + // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider + private class ExternalStorageServiceAnrController implements AnrController { + @Override + public long getAnrDelayMillis(String packageName, int uid) { + int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0); + Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms"); + return delay; + } + + @Override + public void onAnrDelayStarted(String packageName, int uid) { + Log.d(TAG, "onAnrDelayStarted: " + packageName); + } + + @Override + public boolean onAnrDelayCompleted(String packageName, int uid) { + boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true); + Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show); + return show; } } diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 95af84293377..a09dbc7e599d 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -21,7 +21,9 @@ } ], "file_patterns": ["NotificationManagerService\\.java"] - }, + } + ], + "presubmit-large": [ { "name": "CtsScopedStorageCoreHostTest", "file_patterns": ["StorageManagerService\\.java"] diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index b09b6ca61377..5a5f1a3f3723 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -313,9 +313,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private List<PhysicalChannelConfig> mPhysicalChannelConfigs; - private boolean mIsDataEnabled = false; + private boolean[] mIsDataEnabled; - private int mDataEnabledReason; + private int[] mDataEnabledReason; private Map<Integer, Long> mAllowedNetworkTypesList; @@ -524,6 +524,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones); mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones); mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones); + mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones); + mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones); // ds -> ss switch. if (mNumPhones < oldNumPhones) { @@ -565,6 +567,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mPreciseDataConnectionStates.add(new ArrayMap<>()); mBarringInfo.add(i, new BarringInfo()); mTelephonyDisplayInfos[i] = null; + mIsDataEnabled[i] = false; + mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); } } @@ -626,6 +630,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones]; mPhysicalChannelConfigs = new ArrayList<>(); mAllowedNetworkTypesList = new HashMap<>(); + mIsDataEnabled = new boolean[numPhones]; + mDataEnabledReason = new int[numPhones]; for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -655,6 +661,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mPreciseDataConnectionStates.add(new ArrayMap<>()); mBarringInfo.add(i, new BarringInfo()); mTelephonyDisplayInfos[i] = null; + mIsDataEnabled[i] = false; + mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); } @@ -1150,7 +1158,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (events.contains( PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) { try { - r.callback.onDataEnabledChanged(mIsDataEnabled, mDataEnabledReason); + r.callback.onDataEnabledChanged( + mIsDataEnabled[phoneId], mDataEnabledReason[phoneId]); } catch (RemoteException ex) { remove(r.binder); } @@ -2370,30 +2379,36 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { /** * Notify that the data enabled has changed. * + * @param phoneId the phone id. + * @param subId the subId. * @param enabled True if data is enabled, otherwise disabled. * @param reason Reason for data enabled/disabled. See {@code DATA_*} in * {@link TelephonyManager}. */ - public void notifyDataEnabled(boolean enabled, + public void notifyDataEnabled(int phoneId, int subId, boolean enabled, @TelephonyManager.DataEnabledReason int reason) { if (!checkNotifyPermission("notifyDataEnabled()")) { return; } if (VDBG) { - log("notifyDataEnabled: enabled=" + enabled + " reason=" + reason); + log("notifyDataEnabled: PhoneId=" + phoneId + " subId=" + subId + + " enabled=" + enabled + " reason=" + reason); } - mIsDataEnabled = enabled; - mDataEnabledReason = reason; synchronized (mRecords) { - for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) { - try { - r.callback.onDataEnabledChanged(enabled, reason); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); + if (validatePhoneId(phoneId)) { + mIsDataEnabled[phoneId] = enabled; + mDataEnabledReason[phoneId] = reason; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.EVENT_DATA_ENABLED_CHANGED) + && idMatch(r.subId, subId, phoneId)) { + try { + r.callback.onDataEnabledChanged(enabled, reason); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } } @@ -2481,6 +2496,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]); pw.println("mBarringInfo=" + mBarringInfo.get(i)); pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]); + pw.println("mIsDataEnabled=" + mIsDataEnabled); + pw.println("mDataEnabledReason=" + mDataEnabledReason); pw.decreaseIndent(); } pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); @@ -2491,8 +2508,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mDefaultPhoneId=" + mDefaultPhoneId); pw.println("mDefaultSubId=" + mDefaultSubId); pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs); - pw.println("mIsDataEnabled=" + mIsDataEnabled); - pw.println("mDataEnabledReason=" + mDataEnabledReason); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 329ab9983c90..8d5d3d939e4b 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; + import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -837,7 +839,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { // Notify all registered StatusCallbacks for this subGroup for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { if (isCallbackPermissioned(cbInfo)) { - Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode()); + Binder.withCleanCallingIdentity( + () -> + cbInfo.mCallback.onVcnStatusChanged( + VCN_STATUS_CODE_SAFE_MODE)); } } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 2efc83c4af06..e5ef9353135d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -20,10 +20,35 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; +import static android.os.PowerWhitelistManager.REASON_ACTIVITY_STARTER; +import static android.os.PowerWhitelistManager.REASON_ALLOWLISTED_PACKAGE; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_COMPANION_DEVICE_MANAGER; +import static android.os.PowerWhitelistManager.REASON_DENIED; +import static android.os.PowerWhitelistManager.REASON_DEVICE_DEMO_MODE; +import static android.os.PowerWhitelistManager.REASON_DEVICE_OWNER; +import static android.os.PowerWhitelistManager.REASON_EXEMPTED_PACKAGE; +import static android.os.PowerWhitelistManager.REASON_FGS_BINDING; +import static android.os.PowerWhitelistManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_PERSISTENT; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_PERSISTENT_UI; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_TOP; +import static android.os.PowerWhitelistManager.REASON_PROFILE_OWNER; +import static android.os.PowerWhitelistManager.REASON_START_ACTIVITY_FLAG; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_UID; +import static android.os.PowerWhitelistManager.REASON_UID_VISIBLE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.PowerWhitelistManager.getReasonCodeFromProcState; +import static android.os.PowerWhitelistManager.reasonCodeToString; import static android.os.Process.NFC_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; @@ -43,7 +68,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_E import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -86,6 +110,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; @@ -130,8 +156,6 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; @@ -152,58 +176,6 @@ public final class ActiveServices { private static final boolean SHOW_DUNGEON_NOTIFICATION = false; - public static final int FGS_FEATURE_DENIED = 0; - public static final int FGS_FEATURE_ALLOWED_BY_UID_STATE = 1; - public static final int FGS_FEATURE_ALLOWED_BY_PROC_STATE = 2; - public static final int FGS_FEATURE_ALLOWED_BY_UID_VISIBLE = 3; - public static final int FGS_FEATURE_ALLOWED_BY_FLAG = 4; - public static final int FGS_FEATURE_ALLOWED_BY_SYSTEM_UID = 5; - public static final int FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 6; - public static final int FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION = 7; - public static final int FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN = 8; - public static final int FGS_FEATURE_ALLOWED_BY_FGS_TOKEN = 9; - public static final int FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION = 10; - public static final int FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION = 12; - public static final int FGS_FEATURE_ALLOWED_BY_ALLOWLIST = 13; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER = 14; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST = 15; - public static final int FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION = 16; - public static final int FGS_FEATURE_ALLOWED_BY_FGS_BINDING = 17; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE = 18; - public static final int FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD = 19; - public static final int FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES = 20; - public static final int FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER = 21; - public static final int FGS_FEATURE_ALLOWED_BY_COMPANION_APP = 22; - public static final int FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER = 23; - - @IntDef(flag = true, prefix = { "FGS_FEATURE_" }, value = { - FGS_FEATURE_DENIED, - FGS_FEATURE_ALLOWED_BY_UID_STATE, - FGS_FEATURE_ALLOWED_BY_PROC_STATE, - FGS_FEATURE_ALLOWED_BY_UID_VISIBLE, - FGS_FEATURE_ALLOWED_BY_FLAG, - FGS_FEATURE_ALLOWED_BY_SYSTEM_UID, - FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION, - FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION, - FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN, - FGS_FEATURE_ALLOWED_BY_FGS_TOKEN, - FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION, - FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION, - FGS_FEATURE_ALLOWED_BY_ALLOWLIST, - FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER, - FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST, - FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION, - FGS_FEATURE_ALLOWED_BY_FGS_BINDING, - FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE, - FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD, - FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES, - FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER, - FGS_FEATURE_ALLOWED_BY_COMPANION_APP, - FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER - }) - @Retention(RetentionPolicy.SOURCE) - public @interface FgsFeatureRetCode {} - // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; @@ -275,7 +247,7 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; - // white listed packageName. + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); // TODO: remove this after feature development is done @@ -675,7 +647,7 @@ public final class ActiveServices { if (fgRequired) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) { + if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { String msg = "startForegroundService() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -1768,8 +1740,7 @@ public final class ActiveServices { if (!ignoreForeground) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == FGS_FEATURE_DENIED - && isBgFgsRestrictionEnabled(r)) { + if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { final String msg = "Service.startForeground() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -2250,7 +2221,7 @@ public final class ActiveServices { psr.mAllowlistManager = false; for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { ServiceRecord sr = psr.getRunningServiceAt(i); - if (sr.whitelistManager) { + if (sr.allowlistManager) { psr.mAllowlistManager = true; break; } @@ -2261,7 +2232,7 @@ public final class ActiveServices { final ProcessServiceRecord psr = service.app.mServices; psr.stopService(service); psr.updateBoundClientUids(); - if (service.whitelistManager) { + if (service.allowlistManager) { updateAllowlistManagerLocked(psr); } } @@ -2483,7 +2454,7 @@ public final class ActiveServices { clientPsr.setHasAboveClient(true); } if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - s.whitelistManager = true; + s.allowlistManager = true; } if ((flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) { s.setAllowedBgActivityStartsByBinding(true); @@ -2520,7 +2491,7 @@ public final class ActiveServices { if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { servicePsr.setTreatLikeActivity(true); } - if (s.whitelistManager) { + if (s.allowlistManager) { servicePsr.mAllowlistManager = true; } // This could have made the service more important. @@ -2949,7 +2920,6 @@ public final class ActiveServices { final ServiceRestarter res = new ServiceRestarter(); r = new ServiceRecord(mAm, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res); - r.mRecentCallingPackage = callingPackage; res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -2978,6 +2948,8 @@ public final class ActiveServices { } } if (r != null) { + r.mRecentCallingPackage = callingPackage; + r.mRecentCallingUid = callingUid; if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, r.packageName, r.appInfo.uid)) { String msg = "association not allowed between packages " @@ -3440,12 +3412,13 @@ public final class ActiveServices { if (r.fgRequired) { if (DEBUG_FOREGROUND_SERVICE) { - Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid) + Slog.v(TAG, "Allowlisting " + UserHandle.formatUid(r.appInfo.uid) + " for fg-service launch"); } mAm.tempAllowlistUidLocked(r.appInfo.uid, - SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch", - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED); + SERVICE_START_FOREGROUND_TIMEOUT, PowerWhitelistManager.REASON_SERVICE_LAUNCH, + "fg-service-launch", TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + r.mRecentCallingUid); } if (!mPendingServices.contains(r)) { @@ -3551,7 +3524,7 @@ public final class ActiveServices { } } - if (r.whitelistManager) { + if (r.allowlistManager) { psr.mAllowlistManager = true; } @@ -3941,11 +3914,11 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { psr.updateHasAboveClientLocked(); } - // If this connection requested whitelist management, see if we should + // If this connection requested allowlist management, see if we should // now clear that state. if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - s.updateWhitelistManager(); - if (!s.whitelistManager && s.app != null) { + s.updateAllowlistManager(); + if (!s.allowlistManager && s.app != null) { updateAllowlistManagerLocked(s.app.mServices); } } @@ -5400,13 +5373,13 @@ public final class ActiveServices { } if (!r.mAllowWhileInUsePermissionInFgs - || (r.mAllowStartForeground == FGS_FEATURE_DENIED)) { - final @FgsFeatureRetCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( + || (r.mAllowStartForeground == REASON_DENIED)) { + final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts); if (!r.mAllowWhileInUsePermissionInFgs) { - r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != FGS_FEATURE_DENIED); + r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED); } - if (r.mAllowStartForeground == FGS_FEATURE_DENIED) { + if (r.mAllowStartForeground == REASON_DENIED) { r.mAllowStartForeground = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPackage, callingPid, callingUid, intent, r, allowBackgroundActivityStarts); @@ -5420,37 +5393,37 @@ public final class ActiveServices { * @param callingPackage caller app's package name. * @param callingUid caller app's uid. * @param r the service to start. - * @return {@link FgsFeatureRetCode} + * @return {@link ReasonCode} */ - private @FgsFeatureRetCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, + private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, int callingPid, int callingUid, ServiceRecord r, boolean allowBackgroundActivityStarts) { - int ret = FGS_FEATURE_DENIED; + int ret = REASON_DENIED; final int uidState = mAm.getUidStateLocked(callingUid); - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { - ret = FGS_FEATURE_ALLOWED_BY_UID_STATE; + ret = getReasonCodeFromProcState(uidState); } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Does the calling UID have any visible activity? final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid); if (isCallingUidVisible) { - ret = FGS_FEATURE_ALLOWED_BY_UID_VISIBLE; + ret = REASON_UID_VISIBLE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the allow activity background start flag on? if (allowBackgroundActivityStarts) { - ret = FGS_FEATURE_ALLOWED_BY_FLAG; + ret = REASON_START_ACTIVITY_FLAG; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { boolean isCallerSystem = false; final int callingAppId = UserHandle.getAppId(callingUid); switch (callingAppId) { @@ -5466,15 +5439,15 @@ public final class ActiveServices { } if (isCallerSystem) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; + ret = REASON_SYSTEM_UID; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> { if (pr.uid == callingUid) { if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) { - return FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER; + return REASON_ACTIVITY_STARTER; } } return null; @@ -5484,35 +5457,35 @@ public final class ActiveServices { } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (r.app != null) { ActiveInstrumentation instr = r.app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundActivityStartsPermission) { - ret = FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; } } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_BACKGROUND_ACTIVITY_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final boolean isAllowedPackage = mAllowListWhileInUsePermissionInFgs.contains(callingPackage); if (isAllowedPackage) { - ret = FGS_FEATURE_ALLOWED_BY_ALLOWLIST; + ret = REASON_ALLOWLISTED_PACKAGE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID a device owner app? final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid); if (isDeviceOwner) { - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; + ret = REASON_DEVICE_OWNER; } } return ret; @@ -5527,38 +5500,40 @@ public final class ActiveServices { * @param callingUid caller app's uid. * @param intent intent to start/bind service. * @param r the service to start. - * @return {@link FgsFeatureRetCode} + * @return {@link ReasonCode} */ - private @FgsFeatureRetCode int shouldAllowFgsStartForegroundLocked( - @FgsFeatureRetCode int allowWhileInUse, String callingPackage, int callingPid, + private @ReasonCode int shouldAllowFgsStartForegroundLocked( + @ReasonCode int allowWhileInUse, String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { int ret = allowWhileInUse; + FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason = + r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid); final StringBuilder sb = new StringBuilder(64); final int uidState = mAm.getUidStateLocked(callingUid); - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { sb.append("uidState=").append(uidState); - ret = FGS_FEATURE_ALLOWED_BY_UID_STATE; + ret = getReasonCodeFromProcState(uidState); } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> { if (app.uid == callingUid) { final ProcessStateRecord state = app.mState; - if (state.getAllowedStartFgs() != FGS_FEATURE_DENIED) { + if (state.getAllowedStartFgs() != REASON_DENIED) { return state.getAllowedStartFgs(); } else if (state.isAllowedStartFgsState()) { - return FGS_FEATURE_ALLOWED_BY_PROC_STATE; + return getReasonCodeFromProcState(state.getAllowStartFgsState()); } else if (state.areBackgroundFgsStartsAllowedByToken()) { - return FGS_FEATURE_ALLOWED_BY_FGS_BINDING; + return REASON_FGS_BINDING; } else { final ActiveInstrumentation instr = app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundForegroundServiceStartsPermission) { - return FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION; + return REASON_INSTR_BACKGROUND_FGS_PERMISSION; } } } @@ -5569,55 +5544,59 @@ public final class ActiveServices { } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; + ret = REASON_BACKGROUND_FGS_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(SYSTEM_ALERT_WINDOW, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { - if (mAm.isAllowlistedForFgsStartLOSP(callingUid)) { - // uid is on DeviceIdleController's user/system allowlist - // or AMS's FgsStartTempAllowList. - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; + if (ret == REASON_DENIED) { + FgsStartTempAllowList.TempFgsAllowListEntry entry = + mAm.isAllowlistedForFgsStartLOSP(callingUid); + if (entry != null) { + if (entry == ActivityManagerService.FAKE_TEMP_ALLOWLIST_ENTRY) { + ret = REASON_SYSTEM_ALLOW_LISTED; + } else { + ret = entry.mReasonCode; + } } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (UserManager.isDeviceInDemoMode(mAm.mContext)) { - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE; + ret = REASON_DEVICE_DEMO_MODE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID a profile owner app? final boolean isProfileOwner = mAm.mInternal.isProfileOwner(callingUid); if (isProfileOwner) { - ret = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; + ret = REASON_PROFILE_OWNER; } } // NOTE this should always be the last check. - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (isPackageExemptedFromFgsRestriction(r.appInfo.packageName, r.appInfo.uid) || isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) { - ret = FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES; + ret = REASON_EXEMPTED_PACKAGE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final boolean isCompanionApp = mAm.mInternal.isAssociatedCompanionApp( UserHandle.getUserId(callingUid), callingUid); if (isCompanionApp) { - ret = FGS_FEATURE_ALLOWED_BY_COMPANION_APP; + ret = REASON_COMPANION_DEVICE_MANAGER; } } @@ -5626,7 +5605,15 @@ public final class ActiveServices { + "; callingUid: " + callingUid + "; uidState: " + ProcessList.makeProcStateString(uidState) + "; intent: " + intent - + "; code:" + fgsCodeToString(ret) + + "; code:" + reasonCodeToString(ret) + + "; tempAllowListReason:<" + + (tempAllowListReason == null ? null : + (tempAllowListReason.mReason + + ",reasonCode:" + + reasonCodeToString(tempAllowListReason.mReasonCode) + + ",duration:" + tempAllowListReason.mDuration + + ",callingUid:" + tempAllowListReason.mCallingUid)) + + ">" + "; extra:" + sb.toString() + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + "]"; @@ -5660,62 +5647,11 @@ public final class ActiveServices { return CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, uid); } - static String fgsCodeToString(@FgsFeatureRetCode int code) { - switch (code) { - case FGS_FEATURE_DENIED: - return "DENIED"; - case FGS_FEATURE_ALLOWED_BY_UID_STATE: - return "ALLOWED_BY_UID_STATE"; - case FGS_FEATURE_ALLOWED_BY_PROC_STATE: - return "ALLOWED_BY_PROC_STATE"; - case FGS_FEATURE_ALLOWED_BY_UID_VISIBLE: - return "ALLOWED_BY_UID_VISIBLE"; - case FGS_FEATURE_ALLOWED_BY_FLAG: - return "ALLOWED_BY_FLAG"; - case FGS_FEATURE_ALLOWED_BY_SYSTEM_UID: - return "ALLOWED_BY_SYSTEM_UID"; - case FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION: - return "ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION: - return "ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN: - return "ALLOWED_BY_ACTIVITY_TOKEN"; - case FGS_FEATURE_ALLOWED_BY_FGS_TOKEN: - return "ALLOWED_BY_FGS_TOKEN"; - case FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION: - return "ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION: - return "ALLOWED_BY_BACKGROUND_FGS_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_ALLOWLIST: - return "ALLOWED_BY_ALLOWLIST"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER: - return "ALLOWED_BY_DEVICE_OWNER"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST: - return "ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST"; - case FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION: - return "ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_FGS_BINDING: - return "ALLOWED_BY_FGS_BINDING"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE: - return "ALLOWED_BY_DEVICE_DEMO_MODE"; - case FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD: - return "ALLOWED_BY_PROCESS_RECORD"; - case FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES: - return "FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES"; - case FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER: - return "ALLOWED_BY_ACTIVITY_STARTER"; - case FGS_FEATURE_ALLOWED_BY_COMPANION_APP: - return "ALLOWED_BY_COMPANION_APP"; - case FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER: - return "ALLOWED_BY_PROFILE_OWNER"; - default: - return ""; - } - } - - private static boolean isFgsBgStart(@FgsFeatureRetCode int code) { - return code != FGS_FEATURE_ALLOWED_BY_UID_STATE - && code != FGS_FEATURE_ALLOWED_BY_UID_VISIBLE; + private static boolean isFgsBgStart(@ReasonCode int code) { + return code != REASON_PROC_STATE_PERSISTENT + && code != REASON_PROC_STATE_PERSISTENT_UI + && code != REASON_PROC_STATE_TOP + && code != REASON_UID_VISIBLE; } // TODO: remove this notification after feature development is done @@ -5754,10 +5690,10 @@ public final class ActiveServices { } if (!r.mLoggedInfoAllowStartForeground) { final String msg = "Background started FGS: " - + ((r.mAllowStartForeground != FGS_FEATURE_DENIED) ? "Allowed " : "Disallowed ") + + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ") + r.mInfoAllowStartForeground; Slog.wtfQuiet(TAG, msg); - if (r.mAllowStartForeground != FGS_FEATURE_DENIED) { + if (r.mAllowStartForeground != REASON_DENIED) { Slog.i(TAG, msg); } else { Slog.w(TAG, msg); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 171b20c03689..9d1c83894d46 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -68,7 +68,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false; static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false; static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false; - static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false; + static final boolean DEBUG_ALLOWLISTS = DEBUG_ALL || false; static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : ""; static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5ee0e040019c..7cd494976c94 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -33,7 +33,6 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AppOpsManager.OP_NONE; -import static android.app.BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; @@ -50,8 +49,12 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_UNKNOWN; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.INVALID_UID; import static android.os.Process.NETWORK_STACK_UID; import static android.os.Process.NFC_UID; import static android.os.Process.PHONE_UID; @@ -101,7 +104,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; @@ -142,6 +145,8 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityManager; +import android.app.ActivityManager.PendingIntentInfo; +import android.app.ActivityManager.ProcessCapability; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager.RootTaskInfo; @@ -173,6 +178,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.ProcessMemoryState; import android.app.ProfilerInfo; +import android.app.PropertyInvalidatedCache; import android.app.WaitResult; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManager; @@ -181,7 +187,6 @@ import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; -import android.compat.Compatibility; import android.content.AutofillOptions; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; @@ -251,6 +256,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteCallback; @@ -305,8 +311,8 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessMap; import com.android.internal.app.SystemUserHomeActivity; +import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; -import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.content.PackageHelper; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; @@ -623,7 +629,7 @@ public class ActivityManagerService extends IActivityManager.Stub */ private volatile String mDeviceOwnerName; - private volatile int mDeviceOwnerUid = Process.INVALID_UID; + private volatile int mDeviceOwnerUid = INVALID_UID; /** * Map userId to its companion app uids. @@ -1149,13 +1155,13 @@ public class ActivityManagerService extends IActivityManager.Stub DeviceIdleInternal mLocalDeviceIdleController; /** - * Power-save whitelisted app-ids (not including except-idle-whitelisted ones). + * Power-save allowlisted app-ids (not including except-idle-allowlisted ones). */ @CompositeRWLock({"this", "mProcLock"}) int[] mDeviceIdleAllowlist = new int[0]; /** - * Power-save whitelisted app-ids (including except-idle-whitelisted ones). + * Power-save allowlisted app-ids (including except-idle-allowlisted ones). */ @CompositeRWLock({"this", "mProcLock"}) int[] mDeviceIdleExceptIdleAllowlist = new int[0]; @@ -1171,20 +1177,27 @@ public class ActivityManagerService extends IActivityManager.Stub final long duration; final String tag; final int type; + final @ReasonCode int reasonCode; - PendingTempAllowlist(int targetUid, long duration, String tag, int type) { + PendingTempAllowlist(int targetUid, long duration, @ReasonCode int reasonCode, String tag, + int type) { this.targetUid = targetUid; this.duration = duration; this.tag = tag; this.type = type; + this.reasonCode = reasonCode; } void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TARGET_UID, targetUid); - proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.DURATION_MS, duration); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TARGET_UID, + targetUid); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.DURATION_MS, + duration); proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TAG, tag); proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TYPE, type); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.REASON_CODE, + reasonCode); proto.end(token); } } @@ -1198,6 +1211,9 @@ public class ActivityManagerService extends IActivityManager.Stub @CompositeRWLock({"this", "mProcLock"}) final FgsStartTempAllowList mFgsStartTempAllowList = new FgsStartTempAllowList(); + static final FgsStartTempAllowList.TempFgsAllowListEntry FAKE_TEMP_ALLOWLIST_ENTRY = new + FgsStartTempAllowList.TempFgsAllowListEntry(Long.MAX_VALUE, Long.MAX_VALUE, + REASON_SYSTEM_ALLOW_LISTED, "", INVALID_UID); /** * Information about and control over application operations */ @@ -1819,6 +1835,15 @@ public class ActivityManagerService extends IActivityManager.Stub ncl.start(); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + mAppOpsService.setAppOpsPolicy(appOpsPolicy); + } + public IAppOpsService getAppOpsService() { return mAppOpsService; } @@ -3778,10 +3803,11 @@ public class ActivityManagerService extends IActivityManager.Stub mi.getTotalUss(), mi.getTotalRss(), false, ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration); proc.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), + state != null ? state.getName() : proc.processName, + state != null ? state.getPackage() : proc.info.packageName, mi.getTotalPss(), mi.getTotalUss(), mi.getTotalRss(), @@ -4817,12 +4843,12 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int sendIntentSender(IIntentSender target, IBinder whitelistToken, int code, + public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { if (target instanceof PendingIntentRecord) { return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType, - whitelistToken, finishedReceiver, requiredPermission, options); + allowlistToken, finishedReceiver, requiredPermission, options); } else { if (intent == null) { // Weird case: someone has given us their own custom IIntentSender, and now @@ -4834,7 +4860,7 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { - target.send(code, intent, resolvedType, whitelistToken, null, + target.send(code, intent, resolvedType, allowlistToken, null, requiredPermission, options); } catch (RemoteException e) { } @@ -4859,19 +4885,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public String getPackageForIntentSender(IIntentSender pendingResult) { - if (!(pendingResult instanceof PendingIntentRecord)) { - return null; - } - try { - PendingIntentRecord res = (PendingIntentRecord)pendingResult; - return res.key.packageName; - } catch (ClassCastException e) { - } - return null; - } - - @Override public void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { mPendingIntentController.registerIntentSenderCancelListener(sender, receiver); } @@ -4883,15 +4896,17 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int getUidForIntentSender(IIntentSender sender) { + public PendingIntentInfo getInfoForIntentSender(IIntentSender sender) { if (sender instanceof PendingIntentRecord) { - try { - PendingIntentRecord res = (PendingIntentRecord)sender; - return res.uid; - } catch (ClassCastException e) { - } + PendingIntentRecord res = (PendingIntentRecord) sender; + return new PendingIntentInfo( + res.key.packageName, + res.uid, + (res.key.flags & PendingIntent.FLAG_IMMUTABLE) != 0, + res.key.type); + } else { + throw new IllegalArgumentException(); } - return -1; } @Override @@ -4917,15 +4932,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isIntentSenderImmutable(IIntentSender pendingResult) { - if (pendingResult instanceof PendingIntentRecord) { - final PendingIntentRecord res = (PendingIntentRecord) pendingResult; - return (res.key.flags & PendingIntent.FLAG_IMMUTABLE) != 0; - } - return false; - } - - @Override public boolean isIntentSenderAnActivity(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return false; @@ -4942,33 +4948,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isIntentSenderAForegroundService(IIntentSender pendingResult) { - if (pendingResult instanceof PendingIntentRecord) { - final PendingIntentRecord res = (PendingIntentRecord) pendingResult; - return res.key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE; - } - return false; - } - - @Override - public boolean isIntentSenderAService(IIntentSender pendingResult) { - if (pendingResult instanceof PendingIntentRecord) { - final PendingIntentRecord res = (PendingIntentRecord) pendingResult; - return res.key.type == ActivityManager.INTENT_SENDER_SERVICE; - } - return false; - } - - @Override - public boolean isIntentSenderABroadcast(IIntentSender pendingResult) { - if (pendingResult instanceof PendingIntentRecord) { - final PendingIntentRecord res = (PendingIntentRecord) pendingResult; - return res.key.type == ActivityManager.INTENT_SENDER_BROADCAST; - } - return false; - } - - @Override public Intent getIntentForIntentSender(IIntentSender pendingResult) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "getIntentForIntentSender()"); @@ -5449,7 +5428,7 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (appop) { case AppOpsManager.MODE_ALLOWED: - // If force-background-check is enabled, restrict all apps that aren't whitelisted. + // If force-background-check is enabled, restrict all apps that aren't allowlisted. if (mForceBackgroundCheck && !UserHandle.isCore(uid) && !isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ true)) { @@ -5485,7 +5464,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (uidOnBackgroundAllowlistLOSP(uid)) { if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "App " + uid + "/" + packageName - + " on background whitelist; not restricted in background"); + + " on background allowlist; not restricted in background"); } return ActivityManager.APP_START_MODE_NORMAL; } @@ -5494,7 +5473,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) { if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "App " + uid + "/" + packageName - + " on idle whitelist; not restricted in background"); + + " on idle allowlist; not restricted in background"); } return ActivityManager.APP_START_MODE_NORMAL; } @@ -5582,10 +5561,19 @@ public class ActivityManagerService extends IActivityManager.Stub || mPendingTempAllowlist.indexOfKey(uid) >= 0; } + /** + * Is the uid allowlisted to start FGS? + * @param uid + * @return a TempAllowListEntry if the uid is allowed. + * null if the uid is not allowed. + */ + @Nullable @GuardedBy(anyOf = {"this", "mProcLock"}) - boolean isAllowlistedForFgsStartLOSP(int uid) { - return Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0 - || mFgsStartTempAllowList.isAllowed(uid); + FgsStartTempAllowList.TempFgsAllowListEntry isAllowlistedForFgsStartLOSP(int uid) { + if (Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0) { + return FAKE_TEMP_ALLOWLIST_ENTRY; + } + return mFgsStartTempAllowList.getAllowedDurationAndReason(uid); } /** @@ -6057,13 +6045,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void backgroundWhitelistUid(final int uid) { + public void backgroundAllowlistUid(final int uid) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the OS may call backgroundWhitelistUid()"); + throw new SecurityException("Only the OS may call backgroundAllowlistUid()"); } if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist"); + Slog.i(TAG, "Adding uid " + uid + " to bg uid allowlist"); } synchronized (this) { synchronized (mProcLock) { @@ -6083,10 +6071,18 @@ public class ActivityManagerService extends IActivityManager.Stub abiOverride, zygotePolicyFlags); } - // TODO: Move to ProcessList? @GuardedBy("this") final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) { + return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks, + false /* disableTestApiChecks */, abiOverride, zygotePolicyFlags); + } + + // TODO: Move to ProcessList? + @GuardedBy("this") + final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, + boolean disableHiddenApiChecks, boolean disableTestApiChecks, + String abiOverride, int zygotePolicyFlags) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, @@ -6121,7 +6117,8 @@ public class ActivityManagerService extends IActivityManager.Stub mPersistentStartingProcesses.add(app); mProcessList.startProcessLocked(app, new HostingRecord("added application", customProcess != null ? customProcess : app.processName), - zygotePolicyFlags, disableHiddenApiChecks, abiOverride); + zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks, + abiOverride); } return app; @@ -6624,6 +6621,18 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "getUidProcessState"); + } + + synchronized (mProcLock) { + return mProcessList.getUidProcessCapabilityLOSP(uid); + } + } + + @Override public void registerUidObserver(IUidObserver observer, int which, int cutpoint, String callingPackage) { if (!hasUsageStatsPermission(callingPackage)) { @@ -7239,6 +7248,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long memoryGrowthThreshold = Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD); mProcessList.forEachLruProcessesLOSP(false, proc -> { + if (proc.getThread() == null) { + return; + } final ProcessProfileRecord pr = proc.mProfile; final ProcessStateRecord state = proc.mState; final int setProcState = state.getSetProcState(); @@ -8282,7 +8294,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!TextUtils.isEmpty(packageName)) { final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, "getHistoricalProcessExitReasons"); - if (uid != Process.INVALID_UID) { + if (uid != INVALID_UID) { mProcessList.mAppExitInfoTracker.getExitInfo( packageName, uid, pid, maxNum, results); tombstoneService.collectTombstones(results, uid, pid, maxNum); @@ -8316,7 +8328,7 @@ public class ActivityManagerService extends IActivityManager.Stub int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid, String function) { final long identity = Binder.clearCallingIdentity(); - int uid = Process.INVALID_UID; + int uid = INVALID_UID; try { uid = mPackageManagerInt.getPackageUid(packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); @@ -9208,6 +9220,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(ptw.tag); pw.print(" "); pw.print(ptw.type); + pw.print(" "); + pw.print(ptw.reasonCode); } } } @@ -9863,6 +9877,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (thread != null) { pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **"); pw.flush(); + if (pid == MY_PID) { + PropertyInvalidatedCache.dumpCacheInfo(fd, args); + continue; + } try { TransferPipe tp = new TransferPipe(); try { @@ -10065,6 +10083,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ, ProcessList.VISIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ @@ -10072,7 +10091,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_LABEL = new String[] { "Native", "System", "Persistent", "Persistent Service", "Foreground", - "Visible", "Perceptible", "Perceptible Low", + "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium", "Heavy Weight", "Backup", "A Services", "Home", "Previous", "B Services", "Cached" @@ -10080,7 +10099,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] { "native", "sys", "pers", "persvc", "fore", - "vis", "percept", "perceptl", + "vis", "percept", "perceptl", "perceptm", "heavy", "backup", "servicea", "home", "prev", "serviceb", "cached" @@ -10388,6 +10407,8 @@ public class ActivityManagerService extends IActivityManager.Stub } endTime = SystemClock.currentThreadTimeMillis(); hasSwapPss = mi.hasSwappedOutPss; + memtrackGraphics = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); + memtrackGl = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); @@ -10539,6 +10560,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (!Debug.getMemoryInfo(st.pid, info)) { return; } + memtrackGraphics = info.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); + memtrackGl = info.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp); if (pss == 0) { @@ -12556,7 +12579,7 @@ public class ActivityManagerService extends IActivityManager.Stub BroadcastOptions brOptions = null; if (bOptions != null) { brOptions = new BroadcastOptions(bOptions); - if (brOptions.getTemporaryAppWhitelistDuration() > 0) { + if (brOptions.getTemporaryAppAllowlistDuration() > 0) { // See if the caller is allowed to do this. Note we are checking against // the actual real caller (not whoever provided the operation as say a // PendingIntent), because that who is actually supplied the arguments. @@ -13557,8 +13580,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (disableHiddenApiChecks || disableTestApiChecks) { enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, "disable hidden API checks"); - - enableTestApiAccess(ai.packageName); } final long origId = Binder.clearCallingIdentity(); @@ -13576,8 +13597,8 @@ public class ActivityManagerService extends IActivityManager.Stub mUsageStatsService.reportEvent(ii.targetPackage, userId, UsageEvents.Event.SYSTEM_INTERACTION); } - app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, abiOverride, - ZYGOTE_POLICY_FLAG_EMPTY); + app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, + disableTestApiChecks, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY); } app.setActiveInstrumentation(activeInstr); @@ -13732,25 +13753,6 @@ public class ActivityManagerService extends IActivityManager.Stub app.userId, "finished inst"); } - - disableTestApiAccess(app.info.packageName); - } - - private void enableTestApiAccess(String packageName) { - if (mPlatformCompat != null) { - Compatibility.ChangeConfig config = new Compatibility.ChangeConfig( - Collections.singleton(166236554L /* VMRuntime.ALLOW_TEST_API_ACCESS */), - Collections.emptySet()); - CompatibilityChangeConfig override = new CompatibilityChangeConfig(config); - mPlatformCompat.setOverridesForTest(override, packageName); - } - } - - private void disableTestApiAccess(String packageName) { - if (mPlatformCompat != null) { - mPlatformCompat.clearOverrideForTest(166236554L /* VMRuntime.ALLOW_TEST_API_ACCESS */, - packageName); - } } public void finishInstrumentation(IApplicationThread target, @@ -13959,7 +13961,7 @@ public class ActivityManagerService extends IActivityManager.Stub } void noteUidProcessState(final int uid, final int state, - final @ActivityManager.ProcessCapability int capability) { + final @ProcessCapability int capability) { mBatteryStatsService.noteUidProcessState(uid, state); mAppOpsService.updateUidProcState(uid, state, capability); if (mTrackingAssociations) { @@ -14010,6 +14012,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long uptimeSince = curUptime - mLastPowerCheckUptime; mLastPowerCheckUptime = curUptime; mProcessList.forEachLruProcessesLOSP(false, app -> { + if (app.getThread() == null) { + return; + } if (app.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) { int cpuLimit; long checkDur = curUptime - app.mState.getWhenUnimportant(); @@ -14111,11 +14116,12 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName, uptimeSince, cputimeUsed); app.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write( FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED, app.info.uid, processName, - holder.state.getPackage(), + state != null ? state.getPackage() : app.info.packageName, holder.appVersion); }); return true; @@ -14455,8 +14461,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @GuardedBy("this") void tempAllowlistForPendingIntentLocked(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag) { - if (DEBUG_WHITELISTS) { + long duration, int type, @ReasonCode int reasonCode, String reason) { + if (DEBUG_ALLOWLISTS) { Slog.d(TAG, "tempAllowlistForPendingIntentLocked(" + callerPid + ", " + callerUid + ", " + targetUid + ", " + duration + ", " + type + ")"); } @@ -14475,7 +14481,7 @@ public class ActivityManagerService extends IActivityManager.Stub != PackageManager.PERMISSION_GRANTED && checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callerPid, callerUid) != PackageManager.PERMISSION_GRANTED) { - if (DEBUG_WHITELISTS) { + if (DEBUG_ALLOWLISTS) { Slog.d(TAG, "tempAllowlistForPendingIntentLocked() for target " + targetUid + ": pid " + callerPid + " is not allowed"); } @@ -14484,22 +14490,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } - tempAllowlistUidLocked(targetUid, duration, tag, type); + tempAllowlistUidLocked(targetUid, duration, reasonCode, reason, type, callerUid); } /** * Allowlists {@code targetUid} to temporarily bypass Power Save mode. */ @GuardedBy("this") - void tempAllowlistUidLocked(int targetUid, long duration, String tag, int type) { + void tempAllowlistUidLocked(int targetUid, long duration, @ReasonCode int reasonCode, + String reason, int type, int callingUid) { synchronized (mProcLock) { mPendingTempAllowlist.put(targetUid, - new PendingTempAllowlist(targetUid, duration, tag, type)); + new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type)); setUidTempAllowlistStateLSP(targetUid, true); mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget(); - if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { - mFgsStartTempAllowList.add(targetUid, duration); + if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + mFgsStartTempAllowList.add(targetUid, duration, reasonCode, reason, callingUid); } } } @@ -14525,7 +14532,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i = 0; i < N; i++) { PendingTempAllowlist ptw = list[i]; mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, - ptw.duration, ptw.type, true, ptw.tag); + ptw.duration, ptw.type, true, ptw.reasonCode, ptw.tag); } } @@ -15113,10 +15120,10 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken, - long duration, int type) { - mPendingIntentController.setPendingIntentWhitelistDuration(target, whitelistToken, - duration, type); + public void setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, + long duration, int type, @ReasonCode int reasonCode, @Nullable String reason) { + mPendingIntentController.setPendingIntentAllowlistDuration(target, allowlistToken, + duration, type, reasonCode, reason); } @Override @@ -15126,32 +15133,32 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setPendingIntentAllowBgActivityStarts(IIntentSender target, - IBinder whitelistToken, int flags) { + IBinder allowlistToken, int flags) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "setPendingIntentAllowBgActivityStarts():" + " not a PendingIntentRecord: " + target); return; } synchronized (ActivityManagerService.this) { - ((PendingIntentRecord) target).setAllowBgActivityStarts(whitelistToken, flags); + ((PendingIntentRecord) target).setAllowBgActivityStarts(allowlistToken, flags); } } @Override public void clearPendingIntentAllowBgActivityStarts(IIntentSender target, - IBinder whitelistToken) { + IBinder allowlistToken) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "clearPendingIntentAllowBgActivityStarts():" + " not a PendingIntentRecord: " + target); return; } synchronized (ActivityManagerService.this) { - ((PendingIntentRecord) target).clearAllowBgActivityStarts(whitelistToken); + ((PendingIntentRecord) target).clearAllowBgActivityStarts(allowlistToken); } } @Override - public void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids) { + public void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) { synchronized (ActivityManagerService.this) { synchronized (mProcLock) { mDeviceIdleAllowlist = allAppids; @@ -15161,17 +15168,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, boolean adding, - long durationMs, @TempAllowListType int type) { + public void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, boolean adding, + long durationMs, @TempAllowListType int type, @ReasonCode int reasonCode, + @Nullable String reason, int callingUid) { synchronized (ActivityManagerService.this) { synchronized (mProcLock) { mDeviceIdleTempAllowlist = appids; if (adding) { - if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { - mFgsStartTempAllowList.add(changingUid, durationMs); + if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + mFgsStartTempAllowList.add(changingUid, durationMs, reasonCode, reason, + callingUid); } + setAppIdTempAllowlistStateLSP(changingUid, adding); } - setAppIdTempAllowlistStateLSP(changingUid, adding); } } } @@ -15532,11 +15541,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag) { + public void tempAllowlistForPendingIntent(int callerPid, int callerUid, int targetUid, + long duration, int type, @ReasonCode int reasonCode, String reason) { synchronized (ActivityManagerService.this) { ActivityManagerService.this.tempAllowlistForPendingIntentLocked( - callerPid, callerUid, targetUid, duration, type, tag); + callerPid, callerUid, targetUid, duration, type, reasonCode, reason); } } diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java index 48222cb075cd..a9e557103966 100644 --- a/services/core/java/com/android/server/am/AppErrorDialog.java +++ b/services/core/java/com/android/server/am/AppErrorDialog.java @@ -18,10 +18,7 @@ package com.android.server.am; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; @@ -138,19 +135,6 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen findViewById(com.android.internal.R.id.customPanel).setVisibility(View.VISIBLE); } - @Override - public void onStart() { - super.onStart(); - getContext().registerReceiver(mReceiver, - new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - @Override - protected void onStop() { - super.onStop(); - getContext().unregisterReceiver(mReceiver); - } - private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { setResult(msg.what); @@ -204,15 +188,6 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen } } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - cancel(); - } - } - }; - static class Data { AppErrorResult result; int taskId; diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index e5a5cff409b3..3602f44cd785 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.content.ActivityNotFoundException; @@ -1058,7 +1059,26 @@ class AppErrors { Settings.Secure.ANR_SHOW_BACKGROUND, 0, mService.mUserController.getCurrentUserId()) != 0; if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) { - errState.getDialogController().showAnrDialogs(data); + AnrController anrController = errState.getDialogController().getAnrController(); + if (anrController == null) { + errState.getDialogController().showAnrDialogs(data); + } else { + String packageName = proc.info.packageName; + int uid = proc.info.uid; + boolean showDialog = anrController.onAnrDelayCompleted(packageName, uid); + + if (showDialog) { + Slog.d(TAG, "ANR delay completed. Showing ANR dialog for package: " + + packageName); + errState.getDialogController().showAnrDialogs(data); + } else { + Slog.d(TAG, "ANR delay completed. Cancelling ANR dialog for package: " + + packageName); + errState.setNotResponding(false); + errState.setNotRespondingReport(null); + errState.getDialogController().clearAnrDialogs(); + } + } } else { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.CANT_SHOW); diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java index 77d2898842c6..b233a2ccc6e3 100644 --- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java @@ -181,6 +181,11 @@ final class AppNotRespondingDialog extends BaseErrorDialog implements View.OnCli } }; + @Override + protected void closeDialog() { + mHandler.obtainMessage(FORCE_CLOSE).sendToTarget(); + } + static class Data { final ProcessRecord proc; final ApplicationInfo aInfo; diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index c8630fa52973..f8494d8a7c04 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1327,6 +1327,8 @@ public class AppProfiler { // Get a list of Stats that have vsize > 0 final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0); final int statsCount = stats.size(); + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; for (int i = 0; i < statsCount; i++) { ProcessCpuTracker.Stats st = stats.get(i); long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp); @@ -1337,6 +1339,8 @@ public class AppProfiler { mi.pss = pss; mi.swapPss = swaptrackTmp[1]; mi.memtrack = memtrackTmp[0]; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; memInfos.add(mi); } } @@ -1345,20 +1349,18 @@ public class AppProfiler { long totalPss = 0; long totalSwapPss = 0; long totalMemtrack = 0; - long totalMemtrackGraphics = 0; - long totalMemtrackGl = 0; for (int i = 0, size = memInfos.size(); i < size; i++) { ProcessMemInfo mi = memInfos.get(i); if (mi.pss == 0) { mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp); mi.swapPss = swaptrackTmp[1]; mi.memtrack = memtrackTmp[0]; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; } totalPss += mi.pss; totalSwapPss += mi.swapPss; totalMemtrack += mi.memtrack; - totalMemtrackGraphics += memtrackTmp[1]; - totalMemtrackGl += memtrackTmp[2]; } Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 3ce24712b42f..262e79521e8c 100644 --- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -61,6 +61,11 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog { public void onStop() { } + @Override + protected void closeDialog() { + /* Do nothing */ + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index aabb5877764e..7b5f2cd78947 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -16,16 +16,19 @@ package com.android.server.am; -import com.android.internal.R; - import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import android.view.WindowManager; import android.widget.Button; +import com.android.internal.R; + public class BaseErrorDialog extends AlertDialog { private static final int ENABLE_BUTTONS = 0; private static final int DISABLE_BUTTONS = 1; @@ -44,10 +47,19 @@ public class BaseErrorDialog extends AlertDialog { getWindow().setAttributes(attrs); } + @Override public void onStart() { super.onStart(); mHandler.sendEmptyMessage(DISABLE_BUTTONS); mHandler.sendMessageDelayed(mHandler.obtainMessage(ENABLE_BUTTONS), 1000); + getContext().registerReceiver(mReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } + + @Override + protected void onStop() { + super.onStop(); + getContext().unregisterReceiver(mReceiver); } public boolean dispatchKeyEvent(KeyEvent event) { @@ -84,4 +96,24 @@ public class BaseErrorDialog extends AlertDialog { } } }; + + /** + * Called when received ACTION_CLOSE_SYSTEM_DIALOGS. + */ + protected void closeDialog() { + if (mCancelable) { + cancel(); + } else { + dismiss(); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + closeDialog(); + } + } + }; } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 34ff77494f94..29061930cd84 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -22,6 +22,7 @@ import static android.text.TextUtils.formatSimple; import static com.android.server.am.ActivityManagerDebugConfig.*; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteException; @@ -903,8 +905,9 @@ public final class BroadcastQueue { return false; } - final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r, - @TempAllowListType int type) { + final void scheduleTempAllowlistLocked(int uid, long duration, BroadcastRecord r, + @TempAllowListType int type, @PowerWhitelistManager.ReasonCode int reasonCode, + @Nullable String reason) { if (duration > Integer.MAX_VALUE) { duration = Integer.MAX_VALUE; } @@ -926,10 +929,11 @@ public final class BroadcastQueue { b.append(r.intent.getData()); } if (DEBUG_BROADCAST) { - Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration + Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration + " type=" + type + " : " + b.toString()); } - mService.tempAllowlistUidLocked(uid, duration, b.toString(), type); + mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type, + r.callingUid); } /** @@ -1332,10 +1336,12 @@ public final class BroadcastQueue { // r is guaranteed ordered at this point, so we know finishReceiverLocked() // will get a callback and handle the activity start token lifecycle. } - if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) { - scheduleTempWhitelistLocked(filter.owningUid, - brOptions.getTemporaryAppWhitelistDuration(), r, - brOptions.getTemporaryAppWhitelistType()); + if (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0) { + scheduleTempAllowlistLocked(filter.owningUid, + brOptions.getTemporaryAppAllowlistDuration(), r, + brOptions.getTemporaryAppAllowlistType(), + brOptions.getTemporaryAppAllowlistReasonCode(), + brOptions.getTemporaryAppAllowlistReason()); } } return; @@ -1619,11 +1625,13 @@ public final class BroadcastQueue { } final boolean isActivityCapable = - (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0); + (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0); if (isActivityCapable) { - scheduleTempWhitelistLocked(receiverUid, - brOptions.getTemporaryAppWhitelistDuration(), r, - brOptions.getTemporaryAppWhitelistType()); + scheduleTempAllowlistLocked(receiverUid, + brOptions.getTemporaryAppAllowlistDuration(), r, + brOptions.getTemporaryAppAllowlistType(), + brOptions.getTemporaryAppAllowlistReasonCode(), + brOptions.getTemporaryAppAllowlistReason()); } // Broadcast is being executed, its package can't be stopped. diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 7bdf43c9c744..c5f082a0f9e3 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -66,6 +66,10 @@ public final class CachedAppOptimizer { @VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; @VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5"; @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6"; + @VisibleForTesting static final String KEY_COMPACT_THROTTLE_MIN_OOM_ADJ = + "compact_throttle_min_oom_adj"; + @VisibleForTesting static final String KEY_COMPACT_THROTTLE_MAX_OOM_ADJ = + "compact_throttle_max_oom_adj"; @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE = "compact_statsd_sample_rate"; @VisibleForTesting static final String KEY_FREEZER_STATSD_SAMPLE_RATE = @@ -101,6 +105,10 @@ public final class CachedAppOptimizer { @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ = + ProcessList.CACHED_APP_MIN_ADJ; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ = + ProcessList.CACHED_APP_MAX_ADJ; // The sampling rate to push app compaction events into statsd for upload. @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f; @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L; @@ -186,6 +194,10 @@ public final class CachedAppOptimizer { updateFullDeltaRssThrottle(); } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) { updateProcStateThrottle(); + } else if (KEY_COMPACT_THROTTLE_MIN_OOM_ADJ.equals(name)) { + updateMinOomAdjThrottle(); + } else if (KEY_COMPACT_THROTTLE_MAX_OOM_ADJ.equals(name)) { + updateMaxOomAdjThrottle(); } } } @@ -217,6 +229,12 @@ public final class CachedAppOptimizer { @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6; @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mCompactThrottleMinOomAdj = + DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mCompactThrottleMaxOomAdj = + DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ; + @GuardedBy("mPhenotypeFlagLock") private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION; private volatile boolean mUseFreezer = DEFAULT_USE_FREEZER; @GuardedBy("this") @@ -282,6 +300,7 @@ public final class CachedAppOptimizer { * starts the background thread if necessary. */ public void init() { + // TODO: initialize flags to default and only update them if values are set in DeviceConfig DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener); synchronized (mPhenotypeFlagLock) { @@ -294,6 +313,8 @@ public final class CachedAppOptimizer { updateFullDeltaRssThrottle(); updateProcStateThrottle(); updateUseFreezer(); + updateMinOomAdjThrottle(); + updateMaxOomAdjThrottle(); } } @@ -328,6 +349,8 @@ public final class CachedAppOptimizer { pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); pw.println(" " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS); pw.println(" " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent); + pw.println(" " + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj); + pw.println(" " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj); pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate); pw.println(" " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "=" + mFullAnonRssThrottleKb); @@ -367,12 +390,23 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppFull(ProcessRecord app) { - app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( + // Apply OOM adj score throttle for Full App Compaction. + if ((app.mState.getSetAdj() < mCompactThrottleMinOomAdj + || app.mState.getSetAdj() > mCompactThrottleMaxOomAdj) + && app.mState.getCurAdj() >= mCompactThrottleMinOomAdj + && app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) { + app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL); + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendMessage( mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); - + COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + } else { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for " + app.processName + + " oom adj score changed from " + app.mState.getSetAdj() + + " to " + app.mState.getCurAdj()); + } + } } @GuardedBy("mProcLock") @@ -502,18 +536,6 @@ public final class CachedAppOptimizer { } /** - * Enable or disable the freezer. When enable == false all frozen processes are unfrozen, - * but aren't removed from the freezer. While in this state, processes can be added or removed - * by using Process.setProcessFrozen(), but they wouldn't be actually frozen until the freezer - * is enabled. If enable == true all processes in the freezer are frozen. - * - * @param enable Specify whether to enable (true) or disable (false) the freezer. - * - * @hide - */ - private static native void enableFreezerInternal(boolean enable); - - /** * Informs binder that a process is about to be frozen. If freezer is enabled on a process via * this method, this method will synchronously dispatch all pending transactions to the * specified pid. This method will not add significant latencies when unfreezing. @@ -556,10 +578,6 @@ public final class CachedAppOptimizer { if (state == '1' || state == '0') { supported = true; - // This is a workaround after reverting the cgroup v2 uid/pid hierarchy due to - // http://b/179006802. - // TODO: remove once the uid/pid hierarchy is restored - enableFreezerInternal(true); } else { Slog.e(TAG_AM, "unexpected value in cgroup.freeze"); } @@ -629,6 +647,7 @@ public final class CachedAppOptimizer { @GuardedBy("mPhenotypeFlagLock") private void updateCompactionThrottles() { boolean useThrottleDefaults = false; + // TODO: improve efficiency by calling DeviceConfig only once for all flags. String throttleSomeSomeFlag = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_THROTTLE_1); @@ -647,12 +666,20 @@ public final class CachedAppOptimizer { String throttlePersistentFlag = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_THROTTLE_6); + String throttleMinOomAdjFlag = + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ); + String throttleMaxOomAdjFlag = + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ); if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag) || TextUtils.isEmpty(throttleFullSomeFlag) || TextUtils.isEmpty(throttleFullFullFlag) || TextUtils.isEmpty(throttleBFGSFlag) - || TextUtils.isEmpty(throttlePersistentFlag)) { + || TextUtils.isEmpty(throttlePersistentFlag) + || TextUtils.isEmpty(throttleMinOomAdjFlag) + || TextUtils.isEmpty(throttleMaxOomAdjFlag)) { // Set defaults for all if any are not set. useThrottleDefaults = true; } else { @@ -663,6 +690,8 @@ public final class CachedAppOptimizer { mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag); mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag); mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag); + mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag); + mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag); } catch (NumberFormatException e) { useThrottleDefaults = true; } @@ -675,6 +704,8 @@ public final class CachedAppOptimizer { mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5; mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6; + mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ; + mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ; } } @@ -729,6 +760,28 @@ public final class CachedAppOptimizer { } } + @GuardedBy("mPhenotypeFlagLock") + private void updateMinOomAdjThrottle() { + mCompactThrottleMinOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ); + + // Should only compact cached processes. + if (mCompactThrottleMinOomAdj < ProcessList.CACHED_APP_MIN_ADJ) { + mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ; + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateMaxOomAdjThrottle() { + mCompactThrottleMaxOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ); + + // Should only compact cached processes. + if (mCompactThrottleMaxOomAdj > ProcessList.CACHED_APP_MAX_ADJ) { + mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ; + } + } + private boolean parseProcStateThrottle(String procStateThrottleString) { String[] procStates = TextUtils.split(procStateThrottleString, ","); mProcStateThrottle.clear(); diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java index ef135d55e43c..f23d309e424a 100644 --- a/services/core/java/com/android/server/am/ErrorDialogController.java +++ b/services/core/java/com/android/server/am/ErrorDialogController.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.annotation.Nullable; +import android.app.AnrController; import android.app.Dialog; import android.content.Context; @@ -57,6 +59,13 @@ final class ErrorDialogController { @GuardedBy("mProcLock") private AppWaitingForDebuggerDialog mWaitDialog; + /** + * ANR dialog controller + */ + @GuardedBy("mProcLock") + @Nullable + private AnrController mAnrController; + @GuardedBy("mProcLock") boolean hasCrashDialogs() { return mCrashDialogs != null; @@ -118,6 +127,7 @@ final class ErrorDialogController { } forAllDialogs(mAnrDialogs, Dialog::dismiss); mAnrDialogs = null; + mAnrController = null; } @GuardedBy("mProcLock") @@ -220,6 +230,17 @@ final class ErrorDialogController { }); } + @GuardedBy("mProcLock") + @Nullable + AnrController getAnrController() { + return mAnrController; + } + + @GuardedBy("mProcLock") + void setAnrController(AnrController controller) { + mAnrController = controller; + } + /** * Helper function to collect contexts from crashed app located displays. * diff --git a/services/core/java/com/android/server/am/FgsStartTempAllowList.java b/services/core/java/com/android/server/am/FgsStartTempAllowList.java index 4d8749c05294..1f897b5928ce 100644 --- a/services/core/java/com/android/server/am/FgsStartTempAllowList.java +++ b/services/core/java/com/android/server/am/FgsStartTempAllowList.java @@ -18,24 +18,53 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import android.annotation.Nullable; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.SystemClock; import android.util.Slog; -import android.util.SparseLongArray; +import android.util.SparseArray; /** * List of uids that are temporarily allowed to start FGS from background. */ final class FgsStartTempAllowList { private static final int MAX_SIZE = 100; + + public static final class TempFgsAllowListEntry { + final long mExpirationTime; + final long mDuration; + final @ReasonCode int mReasonCode; + final String mReason; + final int mCallingUid; + + TempFgsAllowListEntry(long expirationTime, long duration, @ReasonCode int reasonCode, + String reason, int callingUid) { + mExpirationTime = expirationTime; + mDuration = duration; + mReasonCode = reasonCode; + mReason = reason; + mCallingUid = callingUid; + } + } + /** - * The key is the uid, the value is expiration elapse time in ms of this temp-allowed uid. + * The key is the uid, the value is a TempAllowListEntry. */ - private final SparseLongArray mTempAllowListFgs = new SparseLongArray(); + private final SparseArray<TempFgsAllowListEntry> mTempAllowListFgs = new SparseArray<>(); FgsStartTempAllowList() { } - void add(int uid, long duration) { + /** + * Add a uid and its duration with reason into the FGS temp-allowlist. + * @param uid + * @param duration temp-allowlisted duration in milliseconds. + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp allowlist, only valid when param adding + * is true. + */ + void add(int uid, long duration, @ReasonCode int reasonCode, @Nullable String reason, + int callingUid) { if (duration <= 0) { Slog.e(TAG_AM, "FgsStartTempAllowList bad duration:" + duration + " uid: " + uid); @@ -48,26 +77,36 @@ final class FgsStartTempAllowList { } final long now = SystemClock.elapsedRealtime(); for (int index = mTempAllowListFgs.size() - 1; index >= 0; index--) { - if (mTempAllowListFgs.valueAt(index) < now) { + if (mTempAllowListFgs.valueAt(index).mExpirationTime < now) { mTempAllowListFgs.removeAt(index); } } - final long existingExpirationTime = mTempAllowListFgs.get(uid, -1); + final TempFgsAllowListEntry existing = mTempAllowListFgs.get(uid); final long expirationTime = now + duration; - if (existingExpirationTime == -1 || existingExpirationTime < expirationTime) { - mTempAllowListFgs.put(uid, expirationTime); + if (existing == null || existing.mExpirationTime < expirationTime) { + mTempAllowListFgs.put(uid, + new TempFgsAllowListEntry(expirationTime, duration, reasonCode, + reason == null ? "" : reason, callingUid)); } } - boolean isAllowed(int uid) { + /** + * Is this uid temp-allowlisted to start FGS. + * @param uid + * @return If uid is in the temp-allowlist, return the {@link TempFgsAllowListEntry}; If not in + * temp-allowlist, return null. + */ + @Nullable + TempFgsAllowListEntry getAllowedDurationAndReason(int uid) { final int index = mTempAllowListFgs.indexOfKey(uid); if (index < 0) { - return false; - } else if (mTempAllowListFgs.valueAt(index) < SystemClock.elapsedRealtime()) { + return null; + } else if (mTempAllowListFgs.valueAt(index).mExpirationTime + < SystemClock.elapsedRealtime()) { mTempAllowListFgs.removeAt(index); - return false; + return null; } else { - return true; + return mTempAllowListFgs.valueAt(index); } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 0a8016cf0556..24953fc9c5d6 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; @@ -41,6 +42,7 @@ import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; +import static android.os.PowerWhitelistManager.REASON_DENIED; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -51,7 +53,6 @@ import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; -import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; @@ -1967,7 +1968,7 @@ public final class OomAdjuster { int clientProcState = cstate.getCurRawProcState(); // pass client's mAllowStartFgs to the app if client is not persistent process. - if (cstate.getAllowedStartFgs() != FGS_FEATURE_DENIED + if (cstate.getAllowedStartFgs() != REASON_DENIED && cstate.getMaxAdj() >= ProcessList.FOREGROUND_APP_ADJ) { state.setAllowStartFgs(cstate.getAllowedStartFgs()); } @@ -1981,6 +1982,21 @@ public final class OomAdjuster { capability |= cstate.getCurCapability(); } + // If an app has network capability by default + // (by having procstate <= BFGS), then the apps it binds to will get + // elevated to a high enough procstate anyway to get network unless they + // request otherwise, so don't propagate the network capability by default + // in this case unless they explicitly request it. + if ((cstate.getCurCapability() & PROCESS_CAPABILITY_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + if ((cr.flags & Context.BIND_ALLOW_NETWORK_ACCESS) != 0) { + capability |= PROCESS_CAPABILITY_NETWORK; + } + } else { + capability |= PROCESS_CAPABILITY_NETWORK; + } + } + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state @@ -2048,6 +2064,10 @@ public final class OomAdjuster { && clientAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && adj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { newAdj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ; + } else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0 + && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ + && adj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) { + newAdj = ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ && adj >= ProcessList.PERCEPTIBLE_APP_ADJ) { @@ -2117,13 +2137,13 @@ public final class OomAdjuster { if (enabled) { if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { // TOP process passes all capabilities to the service. - capability |= PROCESS_CAPABILITY_ALL; + capability |= cstate.getCurCapability(); } else { // TOP process passes no capability to the service. } } else { // TOP process passes all capabilities to the service. - capability |= PROCESS_CAPABILITY_ALL; + capability |= cstate.getCurCapability(); } } } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) { @@ -2448,20 +2468,20 @@ public final class OomAdjuster { case PROCESS_STATE_TOP: return PROCESS_CAPABILITY_ALL; case PROCESS_STATE_BOUND_TOP: - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; case PROCESS_STATE_FOREGROUND_SERVICE: if (psr.hasForegroundServices()) { // Capability from FGS are conditional depending on foreground service type in // manifest file and the mAllowWhileInUsePermissionInFgs flag. - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; } else { // process has no FGS, the PROCESS_STATE_FOREGROUND_SERVICE is from client. // the implicit capability could be removed in the future, client should use // BIND_INCLUDE_CAPABILITY flag. - return PROCESS_CAPABILITY_ALL_IMPLICIT; + return PROCESS_CAPABILITY_ALL_IMPLICIT | PROCESS_CAPABILITY_NETWORK; } case PROCESS_STATE_BOUND_FOREGROUND_SERVICE: - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; default: return PROCESS_CAPABILITY_NONE; } @@ -2541,9 +2561,7 @@ public final class OomAdjuster { && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) { mCachedAppOptimizer.compactAppSome(app); - } else if ((state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ - || state.getSetAdj() > ProcessList.CACHED_APP_MAX_ADJ) - && state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ + } else if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) { mCachedAppOptimizer.compactAppFull(app); } diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index 42172bf7e1df..534bd84a91a3 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; @@ -300,15 +301,16 @@ public class PendingIntentController { } } - void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken, - long duration, int type) { + void setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, + long duration, int type, @PowerWhitelistManager.ReasonCode int reasonCode, + @Nullable String reason) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target); return; } synchronized (mLock) { - ((PendingIntentRecord) target).setWhitelistDurationLocked(whitelistToken, duration, - type); + ((PendingIntentRecord) target).setAllowlistDurationLocked(allowlistToken, duration, + type, reasonCode, reason); } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 0eb48f6da60d..51666acb8134 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -31,13 +31,14 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Pair; import android.util.Slog; import android.util.TimeUtils; @@ -67,7 +68,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { * milliseconds, Integer is allowlist type defined at * {@link android.os.PowerWhitelistManager.TempAllowListType} */ - private ArrayMap<IBinder, Pair<Long, Integer>> mWhitelistDuration; + private ArrayMap<IBinder, TempAllowListDuration> mAllowlistDuration; private RemoteCallbackList<IResultReceiver> mCancelCallbacks; private ArraySet<IBinder> mAllowBgActivityStartsForActivitySender = new ArraySet<>(); private ArraySet<IBinder> mAllowBgActivityStartsForBroadcastSender = new ArraySet<>(); @@ -214,6 +215,21 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } } + static final class TempAllowListDuration { + long duration; + int type; + @ReasonCode int reasonCode; + @Nullable String reason; + + TempAllowListDuration(long _duration, int _type, @ReasonCode int _reasonCode, + String _reason) { + duration = _duration; + type = _type; + reasonCode = _reasonCode; + reason = _reason; + } + } + PendingIntentRecord(PendingIntentController _controller, Key _k, int _u) { controller = _controller; key = _k; @@ -221,18 +237,19 @@ public final class PendingIntentRecord extends IIntentSender.Stub { ref = new WeakReference<>(this); } - void setWhitelistDurationLocked(IBinder whitelistToken, long duration, int type) { + void setAllowlistDurationLocked(IBinder allowlistToken, long duration, int type, + @ReasonCode int reasonCode, @Nullable String reason) { if (duration > 0) { - if (mWhitelistDuration == null) { - mWhitelistDuration = new ArrayMap<>(); + if (mAllowlistDuration == null) { + mAllowlistDuration = new ArrayMap<>(); } - mWhitelistDuration.put(whitelistToken, new Pair(duration, type)); - } else if (mWhitelistDuration != null) { - mWhitelistDuration.remove(whitelistToken); - if (mWhitelistDuration.size() <= 0) { - mWhitelistDuration = null; + mAllowlistDuration.put(allowlistToken, + new TempAllowListDuration(duration, type, reasonCode, reason)); + } else if (mAllowlistDuration != null) { + mAllowlistDuration.remove(allowlistToken); + if (mAllowlistDuration.size() <= 0) { + mAllowlistDuration = null; } - } this.stringName = null; } @@ -280,25 +297,25 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return listeners; } - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - sendInner(code, intent, resolvedType, whitelistToken, finishedReceiver, + sendInner(code, intent, resolvedType, allowlistToken, finishedReceiver, requiredPermission, null, null, 0, 0, 0, options); } - public int sendWithResult(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public int sendWithResult(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - return sendInner(code, intent, resolvedType, whitelistToken, finishedReceiver, + return sendInner(code, intent, resolvedType, allowlistToken, finishedReceiver, requiredPermission, null, null, 0, 0, 0, options); } - public int sendInner(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { if (intent != null) intent.setDefusable(true); if (options != null) options.setDefusable(true); - Pair<Long, Integer> duration = null; + TempAllowListDuration duration = null; Intent finalIntent = null; Intent[] allIntents = null; String[] allResolvedTypes = null; @@ -347,8 +364,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mergedOptions.setCallerOptions(opts); } - if (mWhitelistDuration != null) { - duration = mWhitelistDuration.get(whitelistToken); + if (mAllowlistDuration != null) { + duration = mAllowlistDuration.get(allowlistToken); } if (key.type == ActivityManager.INTENT_SENDER_ACTIVITY @@ -377,7 +394,9 @@ public final class PendingIntentRecord extends IIntentSender.Stub { try { if (duration != null) { StringBuilder tag = new StringBuilder(64); - tag.append("pendingintent:"); + tag.append("setPendingIntentAllowlistDuration,reason:"); + tag.append(duration.reason == null ? "" : duration.reason); + tag.append(",pendingintent:"); UserHandle.formatUid(tag, callingUid); tag.append(":"); if (finalIntent.getAction() != null) { @@ -387,8 +406,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } else if (finalIntent.getData() != null) { tag.append(finalIntent.getData().toSafeString()); } - controller.mAmInternal.tempWhitelistForPendingIntent(callingPid, callingUid, - uid, duration.first, duration.second, tag.toString()); + controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid, + uid, duration.duration, duration.type, duration.reasonCode, tag.toString()); } boolean sendFinish = finishedReceiver != null; @@ -417,7 +436,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { allIntents, allResolvedTypes, resultTo, mergedOptions, userId, false /* validateIncomingUser */, this /* originatingPendingIntent */, - mAllowBgActivityStartsForActivitySender.contains(whitelistToken)); + mAllowBgActivityStartsForActivitySender.contains( + allowlistToken)); } else { res = controller.mAtmInternal.startActivityInPackage(uid, callingPid, callingUid, key.packageName, key.featureId, finalIntent, @@ -426,7 +446,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { false /* validateIncomingUser */, this /* originatingPendingIntent */, mAllowBgActivityStartsForActivitySender.contains( - whitelistToken)); + allowlistToken)); } } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startActivity intent", e); @@ -439,8 +459,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { case ActivityManager.INTENT_SENDER_BROADCAST: try { final boolean allowedByToken = - mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken); - final IBinder bgStartsToken = (allowedByToken) ? whitelistToken : null; + mAllowBgActivityStartsForBroadcastSender.contains(allowlistToken); + final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null; // If a completion callback has been requested, require // that the broadcast be delivered synchronously @@ -460,8 +480,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE: try { final boolean allowedByToken = - mAllowBgActivityStartsForServiceSender.contains(whitelistToken); - final IBinder bgStartsToken = (allowedByToken) ? whitelistToken : null; + mAllowBgActivityStartsForServiceSender.contains(allowlistToken); + final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null; controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType, key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE, @@ -533,18 +553,23 @@ public final class PendingIntentRecord extends IIntentSender.Stub { pw.print(prefix); pw.print("sent="); pw.print(sent); pw.print(" canceled="); pw.println(canceled); } - if (mWhitelistDuration != null) { + if (mAllowlistDuration != null) { pw.print(prefix); - pw.print("whitelistDuration="); - for (int i = 0; i < mWhitelistDuration.size(); i++) { + pw.print("allowlistDuration="); + for (int i = 0; i < mAllowlistDuration.size(); i++) { if (i != 0) { pw.print(", "); } - pw.print(Integer.toHexString(System.identityHashCode(mWhitelistDuration.keyAt(i)))); + TempAllowListDuration entry = mAllowlistDuration.valueAt(i); + pw.print(Integer.toHexString(System.identityHashCode(mAllowlistDuration.keyAt(i)))); pw.print(":"); - TimeUtils.formatDuration(mWhitelistDuration.valueAt(i).first, pw); + TimeUtils.formatDuration(entry.duration, pw); pw.print("/"); - pw.print(mWhitelistDuration.valueAt(i).second); + pw.print(entry.type); + pw.print("/"); + pw.print(PowerWhitelistManager.reasonCodeToString(entry.reasonCode)); + pw.print("/"); + pw.print(entry.reason); } pw.println(); } @@ -572,18 +597,23 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } sb.append(' '); sb.append(key.typeName()); - if (mWhitelistDuration != null) { - sb.append( " (whitelist: "); - for (int i = 0; i < mWhitelistDuration.size(); i++) { + if (mAllowlistDuration != null) { + sb.append(" (allowlist: "); + for (int i = 0; i < mAllowlistDuration.size(); i++) { if (i != 0) { sb.append(","); } + TempAllowListDuration entry = mAllowlistDuration.valueAt(i); sb.append(Integer.toHexString(System.identityHashCode( - mWhitelistDuration.keyAt(i)))); + mAllowlistDuration.keyAt(i)))); sb.append(":"); - TimeUtils.formatDuration(mWhitelistDuration.valueAt(i).first, sb); + TimeUtils.formatDuration(entry.duration, sb); + sb.append("/"); + sb.append(entry.type); + sb.append("/"); + sb.append(PowerWhitelistManager.reasonCodeToString(entry.reasonCode)); sb.append("/"); - sb.append(mWhitelistDuration.valueAt(i).second); + sb.append(entry.reason); } sb.append(")"); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 165312352990..3258f8af0da2 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.am.ProcessRecord.TAG; import android.app.ActivityManager; +import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.content.ComponentName; @@ -418,10 +419,16 @@ class ProcessErrorStateRecord { // Retrieve max ANR delay from AnrControllers without the mService lock since the // controllers might in turn call into apps - long anrDialogDelayMs = mService.mActivityTaskManager.getMaxAnrDelayMillis(aInfo); - if (aInfo != null && aInfo.packageName != null && anrDialogDelayMs > 0) { - Slog.i(TAG, "Delaying ANR dialog for " + aInfo.packageName + " for " + anrDialogDelayMs - + "ms"); + AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); + long anrDialogDelayMs = 0; + if (anrController != null) { + String packageName = aInfo.packageName; + int uid = aInfo.uid; + anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); + // Might execute an async binder call to a system app to show an interim + // ANR progress UI + anrController.onAnrDelayStarted(packageName, uid); + Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); } synchronized (mService) { @@ -440,6 +447,7 @@ class ProcessErrorStateRecord { // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLSP(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); + mDialogController.setAnrController(anrController); } // Notify package manager service to possibly update package state diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 194736fbf911..38330fe770fb 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; @@ -57,6 +58,7 @@ import static com.android.server.am.AppProfiler.TAG_PSS; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppProtoEnums; @@ -228,6 +230,11 @@ public final class ProcessList { // not so perceptible that it affects the user immediately if killed. static final int PERCEPTIBLE_LOW_APP_ADJ = 250; + // This is a process hosting services that are not perceptible to the user but the + // client (system) binding to it requested to treat it as if it is perceptible and avoid killing + // it if possible. + static final int PERCEPTIBLE_MEDIUM_APP_ADJ = 225; + // This is a process only hosting components that are perceptible to the // user, and we really want to avoid killing them, but they are not // immediately visible. An example is background music playback. @@ -1027,6 +1034,9 @@ public final class ProcessList { } else if (setAdj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { return buildOomTag("prcl ", "prcl", null, setAdj, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) { + return buildOomTag("prcm ", "prcm", null, setAdj, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, compact); } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { return buildOomTag("prcp ", "prcp", null, setAdj, ProcessList.PERCEPTIBLE_APP_ADJ, compact); @@ -1759,7 +1769,8 @@ public final class ProcessList { */ @GuardedBy("mService") boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, - int zygotePolicyFlags, boolean disableHiddenApiChecks, String abiOverride) { + int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks, + String abiOverride) { if (app.isPendingStart()) { return true; } @@ -1905,6 +1916,10 @@ public final class ProcessList { throw new IllegalStateException("Invalid API policy: " + policy); } runtimeFlags |= policyBits; + + if (disableTestApiChecks) { + runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY; + } } String useAppImageCache = SystemProperties.get( @@ -2368,7 +2383,8 @@ public final class ProcessList { boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags, String abiOverride) { return startProcessLocked(app, hostingRecord, zygotePolicyFlags, - false /* disableHiddenApiChecks */, abiOverride); + false /* disableHiddenApiChecks */, false /* disableTestApiChecks */, + abiOverride); } @GuardedBy("mService") @@ -4389,6 +4405,7 @@ public final class ProcessList { printOomLevel(pw, "FOREGROUND_APP_ADJ", FOREGROUND_APP_ADJ); printOomLevel(pw, "VISIBLE_APP_ADJ", VISIBLE_APP_ADJ); printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", PERCEPTIBLE_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_MEDIUM_APP_ADJ", PERCEPTIBLE_MEDIUM_APP_ADJ); printOomLevel(pw, "PERCEPTIBLE_LOW_APP_ADJ", PERCEPTIBLE_LOW_APP_ADJ); printOomLevel(pw, "BACKUP_APP_ADJ", BACKUP_APP_ADJ); printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", HEAVY_WEIGHT_APP_ADJ); @@ -4652,13 +4669,26 @@ public final class ProcessList { } } - /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */ + /** + * Returns the uid's process state or {@link ActivityManager#PROCESS_STATE_NONEXISTENT} + * if not running + */ @GuardedBy(anyOf = {"mService", "mProcLock"}) int getUidProcStateLOSP(int uid) { UidRecord uidRec = mActiveUids.get(uid); return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState(); } + /** + * Returns the uid's process capability or {@link ActivityManager#PROCESS_CAPABILITY_NONE} + * if not running + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + @ProcessCapability int getUidProcessCapabilityLOSP(int uid) { + UidRecord uidRec = mActiveUids.get(uid); + return uidRec == null ? PROCESS_CAPABILITY_NONE : uidRec.getCurCapability(); + } + /** Returns the UidRecord for the given uid, if it exists. */ @GuardedBy(anyOf = {"mService", "mProcLock"}) UidRecord getUidRecordLOSP(int uid) { @@ -4743,8 +4773,9 @@ public final class ProcessList { if (!UserHandle.isApp(uidRec.getUid()) || !uidRec.hasInternetPermission) { continue; } - // If process state is not changed, then there's nothing to do. - if (uidRec.getSetProcState() == uidRec.getCurProcState()) { + // If process state and capabilities are not changed, then there's nothing to do. + if (uidRec.getSetProcState() == uidRec.getCurProcState() + && uidRec.getSetCapability() == uidRec.getCurCapability()) { continue; } final int blockState = getBlockStateForUid(uidRec); diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 499fbcb0642d..6d783fc63901 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -23,22 +23,23 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_COMPANION_DEVICE_MANAGER; +import static android.os.PowerWhitelistManager.REASON_DENIED; +import static android.os.PowerWhitelistManager.REASON_DEVICE_OWNER; +import static android.os.PowerWhitelistManager.REASON_PROFILE_OWNER; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_UID; +import static android.os.PowerWhitelistManager.ReasonCode; +import static android.os.PowerWhitelistManager.getReasonCodeFromProcState; +import static android.os.PowerWhitelistManager.reasonCodeToString; import static android.os.Process.NFC_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_COMPANION_APP; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROC_STATE; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; -import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED; -import static com.android.server.am.ActiveServices.fgsCodeToString; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ProcessRecord.TAG; @@ -329,7 +330,7 @@ final class ProcessStateRecord { * Does the process has permission to start FGS from background. */ @GuardedBy("mService") - private @ActiveServices.FgsFeatureRetCode int mAllowStartFgsByPermission; + private @ReasonCode int mAllowStartFgsByPermission = REASON_DENIED; /** * Can this process start FGS from background? @@ -337,7 +338,7 @@ final class ProcessStateRecord { * another process through service binding. */ @GuardedBy("mService") - private @ActiveServices.FgsFeatureRetCode int mAllowStartFgs; + private @ReasonCode int mAllowStartFgs = REASON_DENIED; /** * Debugging: primary thing impacting oom_adj. @@ -1152,44 +1153,47 @@ final class ProcessStateRecord { } @GuardedBy("mService") + int getAllowStartFgsState() { + return mAllowStartFgsState; + } + + @GuardedBy("mService") boolean isAllowedStartFgsState() { return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } @GuardedBy("mService") void setAllowStartFgsByPermission() { - int ret = FGS_FEATURE_DENIED; - if (ret == FGS_FEATURE_DENIED) { - boolean isSystem = false; - final int uid = UserHandle.getAppId(mApp.info.uid); - switch (uid) { - case ROOT_UID: - case SYSTEM_UID: - case NFC_UID: - case SHELL_UID: - isSystem = true; - break; - default: - isSystem = false; - break; - } + int ret = REASON_DENIED; + boolean isSystem = false; + final int uid = UserHandle.getAppId(mApp.info.uid); + switch (uid) { + case ROOT_UID: + case SYSTEM_UID: + case NFC_UID: + case SHELL_UID: + isSystem = true; + break; + default: + isSystem = false; + break; + } - if (isSystem) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; - } + if (isSystem) { + ret = REASON_SYSTEM_UID; } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (ActivityManager.checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_BACKGROUND_ACTIVITY_PERMISSION; } else if (ActivityManager.checkComponentPermission( START_FOREGROUND_SERVICES_FROM_BACKGROUND, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; + ret = REASON_BACKGROUND_FGS_PERMISSION; } else if (ActivityManager.checkComponentPermission(SYSTEM_ALERT_WINDOW, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } } mAllowStartFgs = mAllowStartFgsByPermission = ret; @@ -1197,59 +1201,65 @@ final class ProcessStateRecord { @GuardedBy("mService") void setAllowStartFgs() { - if (mAllowStartFgs != FGS_FEATURE_DENIED) { + if (mAllowStartFgs != REASON_DENIED) { return; } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { if (isAllowedStartFgsState()) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROC_STATE; + mAllowStartFgs = getReasonCodeFromProcState(mAllowStartFgsState); } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // Is the calling UID a device owner app? if (mService.mInternal != null) { if (mService.mInternal.isDeviceOwner(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; + mAllowStartFgs = REASON_DEVICE_OWNER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { if (mService.mInternal != null) { final boolean isCompanionApp = mService.mInternal.isAssociatedCompanionApp( UserHandle.getUserId(mApp.info.uid), mApp.info.uid); if (isCompanionApp) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_COMPANION_APP; + mAllowStartFgs = REASON_COMPANION_DEVICE_MANAGER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // Is the calling UID a profile owner app? if (mService.mInternal != null) { if (mService.mInternal.isProfileOwner(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; + mAllowStartFgs = REASON_PROFILE_OWNER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // uid is on DeviceIdleController's user/system allowlist // or AMS's FgsStartTempAllowList. - if (mService.isAllowlistedForFgsStartLOSP(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; + FgsStartTempAllowList.TempFgsAllowListEntry entry = + mService.isAllowlistedForFgsStartLOSP(mApp.info.uid); + if (entry != null) { + if (entry == ActivityManagerService.FAKE_TEMP_ALLOWLIST_ENTRY) { + mAllowStartFgs = REASON_SYSTEM_ALLOW_LISTED; + } else { + mAllowStartFgs = entry.mReasonCode; + } } } } @GuardedBy("mService") - void setAllowStartFgs(@ActiveServices.FgsFeatureRetCode int allowStartFgs) { + void setAllowStartFgs(@ReasonCode int allowStartFgs) { mAllowStartFgs = allowStartFgs; } @GuardedBy("mService") - @ActiveServices.FgsFeatureRetCode int getAllowedStartFgs() { + @ReasonCode int getAllowedStartFgs() { return mAllowStartFgs; } @@ -1291,9 +1301,9 @@ final class ProcessStateRecord { pw.println(); pw.print(prefix); pw.print("allowStartFgsState="); pw.println(mAllowStartFgsState); - if (mAllowStartFgs != FGS_FEATURE_DENIED) { + if (mAllowStartFgs != REASON_DENIED) { pw.print(prefix); pw.print("allowStartFgs="); - pw.println(fgsCodeToString(mAllowStartFgs)); + pw.println(reasonCodeToString(mAllowStartFgs)); } if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) { pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 485087d29422..3ab95d131fad 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.os.PowerWhitelistManager.REASON_DENIED; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -36,6 +37,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.PowerWhitelistManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -101,7 +103,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ProcessRecord isolatedProc; // keep track of isolated process, if requested ServiceState tracker; // tracking service execution, may be null ServiceState restartTracker; // tracking service restart - boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? + boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? boolean delayed; // are we waiting to start this service in the background? boolean fgRequired; // is the service required to go foreground after starting? boolean fgWaiting; // is a timeout for going foreground already scheduled? @@ -156,11 +158,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // the most recent package that start/bind this service. String mRecentCallingPackage; + // the most recent uid that start/bind this service. + int mRecentCallingUid; // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. - @ActiveServices.FgsFeatureRetCode int mAllowStartForeground; + @PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED; String mInfoAllowStartForeground; + FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason; boolean mLoggedInfoAllowStartForeground; String stringName; // caching of toString @@ -309,7 +314,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (isolatedProc != null) { isolatedProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); } - proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager); + proto.write(ServiceRecordProto.WHITELIST_MANAGER, allowlistManager); proto.write(ServiceRecordProto.DELAYED, delayed); if (isForeground || foregroundId != 0) { long fgToken = proto.start(ServiceRecordProto.FOREGROUND); @@ -410,8 +415,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (isolatedProc != null) { pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); } - if (whitelistManager) { - pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager); + if (allowlistManager) { + pw.print(prefix); pw.print("allowlistManager="); pw.println(allowlistManager); } if (mIsAllowedBgActivityStartsByBinding) { pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByBinding="); @@ -429,6 +434,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.println(mAllowWhileInUsePermissionInFgs); pw.print(prefix); pw.print("recentCallingPackage="); pw.println(mRecentCallingPackage); + pw.print(prefix); pw.print("recentCallingUid="); + pw.println(mRecentCallingUid); pw.print(prefix); pw.print("allowStartForeground="); pw.println(mAllowStartForeground); pw.print(prefix); pw.print("infoAllowStartForeground="); @@ -894,13 +901,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return false; } - public void updateWhitelistManager() { - whitelistManager = false; + public void updateAllowlistManager() { + allowlistManager = false; for (int conni=connections.size()-1; conni>=0; conni--) { ArrayList<ConnectionRecord> cr = connections.valueAt(conni); for (int i=0; i<cr.size(); i++) { if ((cr.get(i).flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - whitelistManager = true; + allowlistManager = true; return; } } diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java index 4cc1fc1b7d09..9dddd658575b 100644 --- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java +++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java @@ -82,6 +82,11 @@ final class StrictModeViolationDialog extends BaseErrorDialog { DISMISS_TIMEOUT); } + @Override + protected void closeDialog() { + mHandler.obtainMessage(ACTION_OK).sendToTarget(); + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { synchronized (mService.mProcLock) { diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 4061df4f3f62..03eddc9634d7 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -44,6 +44,23 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index e97f0b47380a..33bdac270c53 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -23,9 +23,12 @@ import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION; +import static com.android.server.apphibernation.AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityThread; import android.app.IActivityManager; import android.apphibernation.IAppHibernationService; import android.content.BroadcastReceiver; @@ -45,6 +48,8 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -93,6 +98,9 @@ public final class AppHibernationService extends SystemService { private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; private final Injector mInjector; + @VisibleForTesting + boolean mIsServiceEnabled; + /** * Initializes the system service. * <p> @@ -139,6 +147,13 @@ public final class AppHibernationService extends SystemService { initializeGlobalHibernationStates(states); } } + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mIsServiceEnabled = isAppHibernationEnabled(); + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_APP_HIBERNATION, + ActivityThread.currentApplication().getMainExecutor(), + this::onDeviceConfigChanged); + } } /** @@ -149,6 +164,10 @@ public final class AppHibernationService extends SystemService { * @return true if package is hibernating for the user */ boolean isHibernatingForUser(String packageName, int userId) { + if (!checkHibernationEnabled("isHibernatingForUser")) { + return false; + } + userId = handleIncomingUser(userId, "isHibernating"); if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user " @@ -174,6 +193,9 @@ public final class AppHibernationService extends SystemService { * @param packageName package to check */ boolean isHibernatingGlobally(String packageName) { + if (!checkHibernationEnabled("isHibernatingGlobally")) { + return false; + } synchronized (mLock) { GlobalLevelState state = mGlobalHibernationStates.get(packageName); if (state == null) { @@ -192,6 +214,9 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { + if (!checkHibernationEnabled("setHibernatingForUser")) { + return; + } userId = handleIncomingUser(userId, "setHibernating"); if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user " @@ -229,6 +254,9 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingGlobally(String packageName, boolean isHibernating) { + if (!checkHibernationEnabled("setHibernatingGlobally")) { + return; + } synchronized (mLock) { GlobalLevelState state = mGlobalHibernationStates.get(packageName); if (state == null) { @@ -421,6 +449,9 @@ public final class AppHibernationService extends SystemService { private void onPackageAdded(@NonNull String packageName, int userId) { synchronized (mLock) { + if (!mUserStates.contains(userId)) { + return; + } UserLevelState userState = new UserLevelState(); userState.packageName = packageName; mUserStates.get(userId).put(packageName, userState); @@ -434,6 +465,9 @@ public final class AppHibernationService extends SystemService { private void onPackageRemoved(@NonNull String packageName, int userId) { synchronized (mLock) { + if (!mUserStates.contains(userId)) { + return; + } mUserStates.get(userId).remove(packageName); } } @@ -444,6 +478,15 @@ public final class AppHibernationService extends SystemService { } } + private void onDeviceConfigChanged(Properties properties) { + for (String key : properties.getKeyset()) { + if (TextUtils.equals(KEY_APP_HIBERNATION_ENABLED, key)) { + mIsServiceEnabled = isAppHibernationEnabled(); + break; + } + } + } + /** * Private helper method to get the real user id and enforce permission checks. * @@ -461,6 +504,13 @@ public final class AppHibernationService extends SystemService { } } + private boolean checkHibernationEnabled(String methodName) { + if (!mIsServiceEnabled) { + Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName)); + } + return mIsServiceEnabled; + } + private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this); static final class AppHibernationServiceStub extends IAppHibernationService.Stub { @@ -536,7 +586,7 @@ public final class AppHibernationService extends SystemService { public static boolean isAppHibernationEnabled() { return DeviceConfig.getBoolean( NAMESPACE_APP_HIBERNATION, - AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED, + KEY_APP_HIBERNATION_ENABLED, false /* defaultValue */); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a776458d63c5..44dcc205a9d0 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -339,7 +339,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; @GuardedBy("this") - private CheckOpsDelegate mCheckOpsDelegate; + private CheckOpsDelegate mAppOpsPolicy; + + @GuardedBy("this") + private CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher; /** * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never @@ -1770,6 +1773,17 @@ public class AppOpsService extends IAppOpsService.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + synchronized (AppOpsService.this) { + mAppOpsPolicy = appOpsPolicy; + } + } + public void packageRemoved(int uid, String packageName) { synchronized (this) { UidState uidState = mUidStates.get(uid); @@ -2868,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (this) { - return mCheckOpsDelegate; + return (mCheckOpsDelegateDispatcher != null) + ? mCheckOpsDelegateDispatcher.getCheckOpsDelegate() + : null; } } public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (this) { - mCheckOpsDelegate = delegate; + if (delegate != null) { + mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(delegate); + } else { + mCheckOpsDelegateDispatcher = null; + } } } @@ -2889,19 +2909,28 @@ public class AppOpsService extends IAppOpsService.Stub { } private int checkOperationInternal(int code, int uid, String packageName, boolean raw) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkOperationImpl(code, uid, packageName, raw); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkOperation(code, uid, packageName, raw, + delegateDispatcher::checkOperationImpl); + } else { + return policy.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkOperation(code, uid, + packageName, raw, AppOpsService.this::checkOperationImpl); } - return delegate.checkOperation(code, uid, packageName, raw, - AppOpsService.this::checkOperationImpl); + return checkOperationImpl(code, uid, packageName, raw); } - private int checkOperationImpl(int code, int uid, String packageName, - boolean raw) { + private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { verifyIncomingOp(code); verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); @@ -2956,15 +2985,25 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkAudioOperationImpl(code, usage, uid, packageName); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkAudioOperation(code, usage, uid, packageName, + delegateDispatcher::checkAudioOperationImpl); + } else { + return policy.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkAudioOperation(code, usage, + uid, packageName, AppOpsService.this::checkAudioOperationImpl); } - return delegate.checkAudioOperation(code, usage, uid, packageName, - AppOpsService.this::checkAudioOperationImpl); + return checkAudioOperationImpl(code, usage, uid, packageName); } private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { @@ -3078,17 +3117,29 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int noteOperation(int code, int uid, String packageName, String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; - } - if (delegate == null) { - return noteOperationImpl(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; + } + if (policy != null) { + if (delegateDispatcher != null) { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + delegateDispatcher::noteOperationImpl); + } else { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().noteOperation(code, uid, packageName, + attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); } - return delegate.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + return noteOperationImpl(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private int noteOperationImpl(int code, int uid, @Nullable String packageName, @@ -6589,7 +6640,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** * Async task for writing note op stack trace, op code, package name and version to file * More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces} @@ -6726,4 +6776,34 @@ public class AppOpsService extends IAppOpsService.Stub { } } } + + private final class CheckOpsDelegateDispatcher { + private final @NonNull CheckOpsDelegate mCheckOpsDelegate; + + CheckOpsDelegateDispatcher(@NonNull CheckOpsDelegate checkOpsDelegate) { + mCheckOpsDelegate = checkOpsDelegate; + } + + public @NonNull CheckOpsDelegate getCheckOpsDelegate() { + return mCheckOpsDelegate; + } + + public int checkOperationImpl(int code, int uid, String packageName, boolean raw) { + return mCheckOpsDelegate.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + + public int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { + return mCheckOpsDelegate.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + + public int noteOperationImpl(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String message, boolean shouldCollectMessage) { + return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5f3405379715..8363c9d203d5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6958,7 +6958,10 @@ public class AudioService extends IAudioService.Stub private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) { final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { - final int index = update.getVolumeIndex(); + int index = update.getVolumeIndex(); + if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) { + index = safeMediaVolumeIndex(update.mDevice); + } streamState.setIndex(index, update.mDevice, update.mCaller, // trusted as index is always validated before message is posted true /*hasModifyAudioSettings*/); @@ -8570,6 +8573,7 @@ public class AudioService extends IAudioService.Stub public void postDisplaySafeVolumeWarning(int flags) { if (mController == null) return; + flags = flags | AudioManager.FLAG_SHOW_UI; try { mController.displaySafeVolumeWarning(flags); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ReEnrollNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/ReEnrollNotificationUtils.java new file mode 100644 index 000000000000..f35a5208f6ed --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/ReEnrollNotificationUtils.java @@ -0,0 +1,82 @@ +/* + * 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.biometrics.sensors.face; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; + +import com.android.internal.R; + +public class ReEnrollNotificationUtils { + + private static final String NOTIFICATION_TAG = "FaceService"; + private static final int NOTIFICATION_ID = 1; + + public static void showReEnrollmentNotification(@NonNull Context context) { + final NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + + final String name = + context.getString(R.string.face_recalibrate_notification_name); + final String title = + context.getString(R.string.face_recalibrate_notification_title); + final String content = + context.getString(R.string.face_recalibrate_notification_content); + + final Intent intent = new Intent("android.settings.FACE_SETTINGS"); + intent.setPackage("com.android.settings"); + + final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, + 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, + null /* options */, UserHandle.CURRENT); + + final String channelName = "FaceEnrollNotificationChannel"; + + final NotificationChannel channel = new NotificationChannel(channelName, name, + NotificationManager.IMPORTANCE_HIGH); + final Notification notification = new Notification.Builder(context, channelName) + .setSmallIcon(R.drawable.ic_lock) + .setContentTitle(title) + .setContentText(content) + .setSubText(name) + .setOnlyAlertOnce(true) + .setLocalOnly(true) + .setAutoCancel(true) + .setCategory(Notification.CATEGORY_SYSTEM) + .setContentIntent(pendingIntent) + .setVisibility(Notification.VISIBILITY_SECRET) + .build(); + + notificationManager.createNotificationChannel(channel); + notificationManager.notifyAsUser(NOTIFICATION_TAG, + NOTIFICATION_ID, notification, + UserHandle.CURRENT); + } + + public static void cancelNotification(@NonNull Context context) { + final NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + notificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, UserHandle.CURRENT); + } + +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 8f554028ebfd..089cf1e4cee8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -41,6 +41,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.face.ReEnrollNotificationUtils; import com.android.server.biometrics.sensors.face.UsageStats; import java.util.ArrayList; @@ -163,6 +164,9 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements vibrateError(); } break; + case BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL: + ReEnrollNotificationUtils.showReEnrollmentNotification(getContext()); + break; default: break; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 898d81b0c8c4..0eb51fdba159 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -41,6 +41,7 @@ import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.face.FaceUtils; +import com.android.server.biometrics.sensors.face.ReEnrollNotificationUtils; import java.io.IOException; import java.util.ArrayList; @@ -85,6 +86,13 @@ public class FaceEnrollClient extends EnrollClient<ISession> { } @Override + public void start(@NonNull Callback callback) { + super.start(callback); + + ReEnrollNotificationUtils.cancelNotification(getContext()); + } + + @Override public void destroy() { try { AidlNativeHandleUtils.close(mPreviewSurface); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index ee8823e041bc..1b9bd7fd0cea 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -19,7 +19,6 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.NotificationManager; import android.app.SynchronousUserSwitchObserver; import android.app.UserSwitchObserver; import android.content.Context; @@ -71,6 +70,7 @@ import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.RemovalConsumer; import com.android.server.biometrics.sensors.face.FaceUtils; import com.android.server.biometrics.sensors.face.LockoutHalImpl; +import com.android.server.biometrics.sensors.face.ReEnrollNotificationUtils; import com.android.server.biometrics.sensors.face.ServiceProvider; import com.android.server.biometrics.sensors.face.UsageStats; @@ -96,8 +96,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private static final String TAG = "Face10"; private static final int ENROLL_TIMEOUT_SEC = 75; - static final String NOTIFICATION_TAG = "FaceService"; - static final int NOTIFICATION_ID = 1; private boolean mTestHalEnabled; @@ -109,7 +107,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; @NonNull private final LockoutHalImpl mLockoutTracker; @NonNull private final UsageStats mUsageStats; - @NonNull private final NotificationManager mNotificationManager; @NonNull private final Map<Integer, Long> mAuthenticatorIds; @Nullable private IBiometricsFace mDaemon; @NonNull private final HalResultController mHalResultController; @@ -343,7 +340,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mUsageStats = new UsageStats(context); mAuthenticatorIds = new HashMap<>(); mLazyDaemon = Face10.this::getDaemon; - mNotificationManager = mContext.getSystemService(NotificationManager.class); mLockoutTracker = new LockoutHalImpl(); mLockoutResetDispatcher = lockoutResetDispatcher; mHalResultController = new HalResultController(sensorId, context, mHandler, mScheduler, @@ -607,8 +603,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, - UserHandle.CURRENT); + ReEnrollNotificationUtils.cancelNotification(mContext); final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, 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 a4b3ac57a4df..3ca51d32797e 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 @@ -17,12 +17,7 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.Context; -import android.content.Intent; import android.content.res.Resources; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; @@ -32,7 +27,6 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Slog; import com.android.internal.R; @@ -40,6 +34,7 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.face.ReEnrollNotificationUtils; import com.android.server.biometrics.sensors.face.UsageStats; import java.util.ArrayList; @@ -52,7 +47,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private static final String TAG = "FaceAuthenticationClient"; - private final NotificationManager mNotificationManager; + private final UsageStats mUsageStats; private final int[] mBiometricPromptIgnoreList; @@ -72,7 +67,6 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { owner, cookie, requireConfirmation, sensorId, isStrongBiometric, BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */, lockoutTracker, isKeyguard); - mNotificationManager = context.getSystemService(NotificationManager.class); mUsageStats = usageStats; final Resources resources = getContext().getResources(); @@ -188,41 +182,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { mLastAcquire = acquireInfo; if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) { - final String name = - getContext().getString(R.string.face_recalibrate_notification_name); - final String title = - getContext().getString(R.string.face_recalibrate_notification_title); - final String content = - getContext().getString(R.string.face_recalibrate_notification_content); - - final Intent intent = new Intent("android.settings.FACE_SETTINGS"); - intent.setPackage("com.android.settings"); - - final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(getContext(), - 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */, - null /* options */, UserHandle.CURRENT); - - final String channelName = "FaceEnrollNotificationChannel"; - - NotificationChannel channel = new NotificationChannel(channelName, name, - NotificationManager.IMPORTANCE_HIGH); - Notification notification = new Notification.Builder(getContext(), channelName) - .setSmallIcon(R.drawable.ic_lock) - .setContentTitle(title) - .setContentText(content) - .setSubText(name) - .setOnlyAlertOnce(true) - .setLocalOnly(true) - .setAutoCancel(true) - .setCategory(Notification.CATEGORY_SYSTEM) - .setContentIntent(pendingIntent) - .setVisibility(Notification.VISIBILITY_SECRET) - .build(); - - mNotificationManager.createNotificationChannel(channel); - mNotificationManager.notifyAsUser(Face10.NOTIFICATION_TAG, - Face10.NOTIFICATION_ID, notification, - UserHandle.CURRENT); + ReEnrollNotificationUtils.showReEnrollmentNotification(getContext()); } final boolean shouldSend = shouldSend(acquireInfo, vendorCode); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 507600783aa4..2de709ebe71d 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -61,7 +61,6 @@ import android.widget.Toast; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.UiThread; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; @@ -207,7 +206,7 @@ public class ClipboardService extends SystemService { new ClipData.Item(contents)); synchronized(mClipboards) { setPrimaryClipInternal(getClipboard(0), clip, - android.os.Process.SYSTEM_UID); + android.os.Process.SYSTEM_UID, null); } } }); @@ -247,6 +246,8 @@ public class ClipboardService extends SystemService { ClipData primaryClip; /** UID that set {@link #primaryClip}. */ int primaryClipUid = android.os.Process.NOBODY_UID; + /** Package of the app that set {@link #primaryClip}. */ + String mPrimaryClipPackage; final HashSet<String> activePermissionOwners = new HashSet<String>(); @@ -365,7 +366,7 @@ public class ClipboardService extends SystemService { return; } checkDataOwnerLocked(clip, intendingUid); - setPrimaryClipInternal(clip, intendingUid); + setPrimaryClipInternal(clip, intendingUid, callingPackage); } } @@ -378,7 +379,7 @@ public class ClipboardService extends SystemService { intendingUid, intendingUserId)) { return; } - setPrimaryClipInternal(null, intendingUid); + setPrimaryClipInternal(null, intendingUid, callingPackage); } } @@ -509,6 +510,11 @@ public class ClipboardService extends SystemService { } void setPrimaryClipInternal(@Nullable ClipData clip, int uid) { + setPrimaryClipInternal(clip, uid, null); + } + + private void setPrimaryClipInternal( + @Nullable ClipData clip, int uid, @Nullable String sourcePackage) { // Push clipboard to host, if any if (mHostClipboardMonitor != null) { if (clip == null) { @@ -524,7 +530,7 @@ public class ClipboardService extends SystemService { // Update this user final int userId = UserHandle.getUserId(uid); - setPrimaryClipInternal(getClipboard(userId), clip, uid); + setPrimaryClipInternal(getClipboard(userId), clip, uid, sourcePackage); // Update related users List<UserInfo> related = getRelatedProfiles(userId); @@ -558,7 +564,8 @@ public class ClipboardService extends SystemService { final boolean canCopyIntoProfile = !hasRestriction( UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id); if (canCopyIntoProfile) { - setPrimaryClipInternal(getClipboard(id), clip, uid); + setPrimaryClipInternal( + getClipboard(id), clip, uid, sourcePackage); } } } @@ -568,6 +575,11 @@ public class ClipboardService extends SystemService { void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, int uid) { + setPrimaryClipInternal(clipboard, clip, uid, null); + } + + private void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, + int uid, @Nullable String sourcePackage) { revokeUris(clipboard); clipboard.activePermissionOwners.clear(); if (clip == null && clipboard.primaryClip == null) { @@ -576,8 +588,10 @@ public class ClipboardService extends SystemService { clipboard.primaryClip = clip; if (clip != null) { clipboard.primaryClipUid = uid; + clipboard.mPrimaryClipPackage = sourcePackage; } else { clipboard.primaryClipUid = android.os.Process.NOBODY_UID; + clipboard.mPrimaryClipPackage = null; } if (clip != null) { final ClipDescription description = clip.getDescription(); @@ -861,29 +875,34 @@ public class ClipboardService extends SystemService { && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) { return; } - // Load the labels for the calling app and the app that set the clipboard content. - final long ident = Binder.clearCallingIdentity(); + + // Retrieve the app label of the source of the clip data + CharSequence sourceAppLabel = null; + if (clipboard.mPrimaryClipPackage != null) { + try { + sourceAppLabel = mPm.getApplicationLabel( + mPm.getApplicationInfo(clipboard.mPrimaryClipPackage, 0)); + } catch (PackageManager.NameNotFoundException e) { + // leave label as null + } + } + try { - final IPackageManager pm = AppGlobals.getPackageManager(); + CharSequence callingAppLabel = mPm.getApplicationLabel( + mPm.getApplicationInfo(callingPackage, 0)); String message; - final CharSequence callingLabel = mPm.getApplicationLabel( - pm.getApplicationInfo(callingPackage, 0, userId)); - final String[] packagesForUid = pm.getPackagesForUid(clipboard.primaryClipUid); - if (packagesForUid != null && packagesForUid.length > 0) { - final CharSequence clipLabel = mPm.getApplicationLabel( - pm.getApplicationInfo(packagesForUid[0], 0, - UserHandle.getUserId(clipboard.primaryClipUid))); - message = callingLabel + " pasted from " + clipLabel; + if (sourceAppLabel != null) { + message = callingAppLabel + " pasted from " + sourceAppLabel; } else { - message = callingLabel + " pasted from clipboard"; + message = callingAppLabel + " pasted from clipboard"; } Slog.i(TAG, message); - Toast.makeText(getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT) - .show(); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(ident); + Binder.withCleanCallingIdentity(() -> + Toast.makeText(getContext(), UiThread.get().getLooper(), message, + Toast.LENGTH_SHORT) + .show()); + } catch (PackageManager.NameNotFoundException e) { + // do nothing } } } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 46c49e7fc28c..641287f0f435 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -16,6 +16,8 @@ package com.android.server.connectivity; +import static com.android.net.module.util.CollectionUtils.contains; + import android.annotation.NonNull; import android.net.ConnectivityManager; import android.net.IDnsResolver; @@ -33,7 +35,6 @@ import android.os.ServiceSpecificException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.net.module.util.NetworkStackConstants; import com.android.server.net.BaseNetworkObserver; @@ -117,8 +118,8 @@ public class Nat464Xlat extends BaseNetworkObserver { @VisibleForTesting protected static boolean requiresClat(NetworkAgentInfo nai) { // TODO: migrate to NetworkCapabilities.TRANSPORT_*. - final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType()); - final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState()); + final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType()); + final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState()); // Only run clat on networks that have a global IPv6 address and don't have a native IPv4 // address. diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 8bf188696c27..9411e33434d8 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -28,6 +28,8 @@ import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; +import static com.android.net.module.util.CollectionUtils.toIntArray; + import android.annotation.NonNull; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -40,23 +42,21 @@ import android.net.UidRange; import android.os.Build; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; import android.system.OsConstants; -import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.CollectionUtils; import com.android.server.LocalServices; -import com.android.server.SystemConfig; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -80,6 +80,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final PackageManager mPackageManager; private final UserManager mUserManager; + private final SystemConfigManager mSystemConfigManager; private final INetd mNetd; private final Dependencies mDeps; @@ -123,6 +124,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse @NonNull final Dependencies deps) { mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mSystemConfigManager = context.getSystemService(SystemConfigManager.class); mNetd = netd; mDeps = deps; } @@ -174,20 +176,18 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */)); - final SparseArray<ArraySet<String>> systemPermission = - SystemConfig.getInstance().getSystemPermissions(); - for (int i = 0; i < systemPermission.size(); i++) { - ArraySet<String> perms = systemPermission.valueAt(i); - int uid = systemPermission.keyAt(i); - int netdPermission = 0; - // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission. - if (perms != null) { - netdPermission |= perms.contains(UPDATE_DEVICE_STATS) - ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0; - netdPermission |= perms.contains(INTERNET) - ? INetd.PERMISSION_INTERNET : 0; + final SparseArray<String> netdPermToSystemPerm = new SparseArray<>(); + netdPermToSystemPerm.put(INetd.PERMISSION_INTERNET, INTERNET); + netdPermToSystemPerm.put(INetd.PERMISSION_UPDATE_DEVICE_STATS, UPDATE_DEVICE_STATS); + for (int i = 0; i < netdPermToSystemPerm.size(); i++) { + final int netdPermission = netdPermToSystemPerm.keyAt(i); + final String systemPermission = netdPermToSystemPerm.valueAt(i); + final int[] hasPermissionUids = + mSystemConfigManager.getSystemPermissionUids(systemPermission); + for (int j = 0; j < hasPermissionUids.length; j++) { + final int uid = hasPermissionUids[j]; + netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission); } - netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission); } log("Users: " + mUsers.size() + ", Apps: " + mApps.size()); update(mUsers, mApps, true); @@ -204,7 +204,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) { return false; } - final int index = ArrayUtils.indexOf(app.requestedPermissions, permission); + final int index = CollectionUtils.indexOf(app.requestedPermissions, permission); if (index < 0 || index >= app.requestedPermissionsFlags.length) return false; return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0; } @@ -246,15 +246,6 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse return mApps.containsKey(uid); } - private int[] toIntArray(Collection<Integer> list) { - int[] array = new int[list.size()]; - int i = 0; - for (Integer item : list) { - array[i++] = item; - } - return array; - } - private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) { List<Integer> network = new ArrayList<>(); List<Integer> system = new ArrayList<>(); @@ -662,23 +653,23 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse if (allPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids( INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS, - ArrayUtils.convertToIntArray(allPermissionAppIds)); + toIntArray(allPermissionAppIds)); } if (internetPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET, - ArrayUtils.convertToIntArray(internetPermissionAppIds)); + toIntArray(internetPermissionAppIds)); } if (updateStatsPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS, - ArrayUtils.convertToIntArray(updateStatsPermissionAppIds)); + toIntArray(updateStatsPermissionAppIds)); } if (noPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE, - ArrayUtils.convertToIntArray(noPermissionAppIds)); + toIntArray(noPermissionAppIds)); } if (uninstalledAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED, - ArrayUtils.convertToIntArray(uninstalledAppIds)); + toIntArray(uninstalledAppIds)); } } catch (RemoteException e) { Log.e(TAG, "Pass appId list of special permission failed." + e); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index a769e88f77d7..01ac81fb2cb5 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -113,6 +113,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; +import com.android.net.module.util.NetdUtils; import com.android.net.module.util.NetworkStackConstants; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; @@ -1509,7 +1510,7 @@ public class Vpn { if (start != -1) ranges.add(new UidRange(start, stop)); } else if (disallowedApplications != null) { // Add all ranges for user skipping UIDs for disallowedApplications. - final UidRange userRange = UidRange.createForUser(userId); + final UidRange userRange = UidRange.createForUser(UserHandle.of(userId)); int start = userRange.start; for (int uid : getAppsUids(disallowedApplications, userId)) { if (uid == start) { @@ -1522,7 +1523,7 @@ public class Vpn { if (start <= userRange.stop) ranges.add(new UidRange(start, userRange.stop)); } else { // Add all UIDs for the user. - ranges.add(UidRange.createForUser(userId)); + ranges.add(UidRange.createForUser(UserHandle.of(userId))); } } @@ -1531,7 +1532,7 @@ public class Vpn { private static List<UidRange> uidRangesForUser(int userId, Set<UidRange> existingRanges) { // UidRange#createForUser returns the entire range of UIDs available to a macro-user. // This is something like 0-99999 ; {@see UserHandle#PER_USER_RANGE} - final UidRange userRange = UidRange.createForUser(userId); + final UidRange userRange = UidRange.createForUser(UserHandle.of(userId)); final List<UidRange> ranges = new ArrayList<>(); for (UidRange range : existingRanges) { if (userRange.containsRange(range)) { @@ -2528,7 +2529,7 @@ public class Vpn { address /* unused */, address /* unused */, network); - mNms.setInterfaceUp(mTunnelIface.getInterfaceName()); + NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName()); mSession = mIkev2SessionCreator.createIkeSession( mContext, diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index fe89903c02d7..7b9ca37b1639 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -264,6 +264,10 @@ public class SyncManager { private final SyncLogger mLogger; + // NOTE: this is a temporary allow-list for testing purposes; it will be removed before release. + private final String[] mEjSyncAllowedPackages = new String[]{ + "com.google.android.google", "com.android.frameworks.servicestests"}; + private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) { for (JobInfo job: pendingJobs) { if (job.getId() == jobId) { @@ -983,6 +987,14 @@ public class SyncManager { } } + final boolean scheduleAsEj = + extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false); + // NOTE: this is a temporary check for internal testing - to be removed before release. + if (scheduleAsEj && !ArrayUtils.contains(mEjSyncAllowedPackages, callingPackage)) { + throw new IllegalArgumentException( + callingPackage + " is not allowed to schedule a sync as an EJ yet."); + } + for (AccountAndUser account : accounts) { // If userId is specified, do not sync accounts of other users if (userId >= UserHandle.USER_SYSTEM && account.userId >= UserHandle.USER_SYSTEM @@ -1490,6 +1502,12 @@ public class SyncManager { + logSafe(syncOperation.target)); backoff = new Pair<Long, Long>(SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); + } else { + // if an EJ is being backed-off but doesn't have SYNC_EXTRAS_IGNORE_BACKOFF set, + // reschedule it as a regular job + if (syncOperation.isScheduledAsExpeditedJob()) { + syncOperation.scheduleEjAsRegularJob = true; + } } long now = SystemClock.elapsedRealtime(); long backoffDelay = backoff.first == SyncStorageEngine.NOT_IN_BACKOFF_MODE ? 0 @@ -1640,6 +1658,10 @@ public class SyncManager { b.setRequiresCharging(true); } + if (syncOperation.isScheduledAsExpeditedJob() && !syncOperation.scheduleEjAsRegularJob) { + b.setExpedited(true); + } + if (syncOperation.syncExemptionFlag == ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP) { DeviceIdleInternal dic = @@ -3951,6 +3973,9 @@ public class SyncManager { if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) { return true; } + if (key.equals(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB)) { + return true; + } if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS)) { return true; } diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 478763531abc..c8654d1b36ee 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -103,6 +103,13 @@ public class SyncOperation { /** Stores the number of times this sync operation failed and had to be retried. */ int retries; + /** + * Indicates if a sync that was originally scheduled as an EJ is being re-scheduled as a + * regular job. Specifically, this will be {@code true} if a sync is being backed-off but + * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF} is not set. + */ + boolean scheduleEjAsRegularJob; + /** jobId of the JobScheduler job corresponding to this sync */ public int jobId; @@ -408,6 +415,12 @@ public class SyncOperation { if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { sb.append(" EXPEDITED"); } + if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false)) { + sb.append(" EXPEDITED-JOB"); + if (scheduleEjAsRegularJob) { + sb.append("(scheduled-as-regular)"); + } + } switch (syncExemptionFlag) { case ContentResolver.SYNC_EXEMPTION_NONE: break; @@ -537,6 +550,11 @@ public class SyncOperation { return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, false); } + boolean isScheduledAsExpeditedJob() { + return mImmutableExtras.getBoolean( + ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false); + } + boolean isAppStandbyExempted() { return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE; } diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index a62f67a743ad..30cbf2745638 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -61,7 +61,10 @@ public abstract class BrightnessMappingStrategy { private static final Plog PLOG = Plog.createSystemPlog(TAG); @Nullable - public static BrightnessMappingStrategy create(Resources resources) { + public static BrightnessMappingStrategy create(Resources resources, + DisplayDeviceConfig displayDeviceConfig) { + + // Display independent values float[] luxLevels = getLuxLevels(resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)); int[] brightnessLevelsBacklight = resources.getIntArray( @@ -71,32 +74,22 @@ public abstract class BrightnessMappingStrategy { float autoBrightnessAdjustmentMaxGamma = resources.getFraction( com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1); - - float[] nitsRange = getFloatArray(resources.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)); - int[] backlightRange = resources.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight); - long shortTermModelTimeout = resources.getInteger( com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout); - if (isValidMapping(nitsRange, backlightRange) + // Display dependent values - used for physical mapping strategy nits -> brightness + final float[] nitsRange = displayDeviceConfig.getNits(); + final float[] brightnessRange = displayDeviceConfig.getBrightness(); + + if (isValidMapping(nitsRange, brightnessRange) && isValidMapping(luxLevels, brightnessLevelsNits)) { - int minimumBacklight = resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMinimum); - int maximumBacklight = resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMaximum); - if (backlightRange[0] > minimumBacklight - || backlightRange[backlightRange.length - 1] < maximumBacklight) { - Slog.w(TAG, "Screen brightness mapping does not cover whole range of available " + - "backlight values, autobrightness functionality may be impaired."); - } + BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( luxLevels, brightnessLevelsNits); builder.setShortTermModelTimeoutMillis(shortTermModelTimeout); builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); - return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange, + return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange, autoBrightnessAdjustmentMaxGamma); } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) { return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight, @@ -264,11 +257,11 @@ public abstract class BrightnessMappingStrategy { public abstract boolean setAutoBrightnessAdjustment(float adjustment); /** - * Converts the provided backlight value to nits if possible. + * Converts the provided brightness value to nits if possible. * - * Returns -1.0f if there's no available mapping for the backlight to nits. + * Returns -1.0f if there's no available mapping for the brightness to nits. */ - public abstract float convertToNits(int backlight); + public abstract float convertToNits(float brightness); /** * Adds a user interaction data point to the brightness mapping. @@ -603,7 +596,7 @@ public abstract class BrightnessMappingStrategy { } @Override - public float convertToNits(int backlight) { + public float convertToNits(float brightness) { return -1.0f; } @@ -701,37 +694,39 @@ public abstract class BrightnessMappingStrategy { // in nits. private Spline mBrightnessSpline; - // A spline mapping from nits to the corresponding backlight value, normalized to the range + // A spline mapping from nits to the corresponding brightness value, normalized to the range // [0, 1.0]. - private Spline mNitsToBacklightSpline; + private Spline mNitsToBrightnessSpline; + + // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to + // a brightness in nits. + private Spline mBrightnessToNitsSpline; // The default brightness configuration. private final BrightnessConfiguration mDefaultConfig; - // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to - // a brightness in nits. - private Spline mBacklightToNitsSpline; - - private float[] mNits; - private int[] mBacklight; + private final float[] mNits; + private final float[] mBrightness; private boolean mBrightnessRangeAdjustmentApplied; - private float mMaxGamma; + private final float mMaxGamma; private float mAutoBrightnessAdjustment; private float mUserLux; private float mUserBrightness; public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, - int[] backlight, float maxGamma) { - Preconditions.checkArgument(nits.length != 0 && backlight.length != 0, - "Nits and backlight arrays must not be empty!"); - Preconditions.checkArgument(nits.length == backlight.length, - "Nits and backlight arrays must be the same length!"); + float[] brightness, float maxGamma) { + + Preconditions.checkArgument(nits.length != 0 && brightness.length != 0, + "Nits and brightness arrays must not be empty!"); + + Preconditions.checkArgument(nits.length == brightness.length, + "Nits and brightness arrays must be the same length!"); Objects.requireNonNull(config); Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits"); - Preconditions.checkArrayElementsInRange(backlight, - PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight"); + Preconditions.checkArrayElementsInRange(brightness, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness"); mMaxGamma = maxGamma; mAutoBrightnessAdjustment = 0; @@ -739,7 +734,7 @@ public abstract class BrightnessMappingStrategy { mUserBrightness = -1; mNits = nits; - mBacklight = backlight; + mBrightness = brightness; computeNitsBrightnessSplines(mNits); mDefaultConfig = config; @@ -784,15 +779,15 @@ public abstract class BrightnessMappingStrategy { public float getBrightness(float lux, String packageName, @ApplicationInfo.Category int category) { float nits = mBrightnessSpline.interpolate(lux); - float backlight = mNitsToBacklightSpline.interpolate(nits); + float brightness = mNitsToBrightnessSpline.interpolate(nits); // Correct the brightness according to the current application and its category, but - // only if no user data point is set (as this will oevrride the user setting). + // only if no user data point is set (as this will override the user setting). if (mUserLux == -1) { - backlight = correctBrightness(backlight, packageName, category); + brightness = correctBrightness(brightness, packageName, category); } else if (mLoggingEnabled) { Slog.d(TAG, "user point set, correction not applied"); } - return backlight; + return brightness; } @Override @@ -817,8 +812,8 @@ public abstract class BrightnessMappingStrategy { } @Override - public float convertToNits(int backlight) { - return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight)); + public float convertToNits(float brightness) { + return mBrightnessToNitsSpline.interpolate(brightness); } @Override @@ -884,7 +879,8 @@ public abstract class BrightnessMappingStrategy { pw.println("PhysicalMappingStrategy"); pw.println(" mConfig=" + mConfig); pw.println(" mBrightnessSpline=" + mBrightnessSpline); - pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline); + pw.println(" mNitsToBrightnessSpline=" + mNitsToBrightnessSpline); + pw.println(" mBrightnessToNitsSpline=" + mBrightnessToNitsSpline); pw.println(" mMaxGamma=" + mMaxGamma); pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); pw.println(" mUserLux=" + mUserLux); @@ -894,31 +890,25 @@ public abstract class BrightnessMappingStrategy { } private void computeNitsBrightnessSplines(float[] nits) { - final int len = nits.length; - float[] normalizedBacklight = new float[len]; - for (int i = 0; i < len; i++) { - normalizedBacklight[i] = normalizeAbsoluteBrightness(mBacklight[i]); - } - - mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); - mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); + mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness); + mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits); } private void computeSpline() { Pair<float[], float[]> defaultCurve = mConfig.getCurve(); float[] defaultLux = defaultCurve.first; float[] defaultNits = defaultCurve.second; - float[] defaultBacklight = new float[defaultNits.length]; - for (int i = 0; i < defaultBacklight.length; i++) { - defaultBacklight[i] = mNitsToBacklightSpline.interpolate(defaultNits[i]); + float[] defaultBrightness = new float[defaultNits.length]; + for (int i = 0; i < defaultBrightness.length; i++) { + defaultBrightness[i] = mNitsToBrightnessSpline.interpolate(defaultNits[i]); } - Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBacklight, mUserLux, + Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux, mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma); float[] lux = curve.first; - float[] backlight = curve.second; - float[] nits = new float[backlight.length]; + float[] brightness = curve.second; + float[] nits = new float[brightness.length]; for (int i = 0; i < nits.length; i++) { - nits[i] = mBacklightToNitsSpline.interpolate(backlight[i]); + nits[i] = mBrightnessToNitsSpline.interpolate(brightness[i]); } mBrightnessSpline = Spline.createSpline(lux, nits); } @@ -926,7 +916,7 @@ public abstract class BrightnessMappingStrategy { private float getUnadjustedBrightness(float lux) { Pair<float[], float[]> curve = mConfig.getCurve(); Spline spline = Spline.createSpline(curve.first, curve.second); - return mNitsToBacklightSpline.interpolate(spline.interpolate(lux)); + return mNitsToBrightnessSpline.interpolate(spline.interpolate(lux)); } private float correctBrightness(float brightness, String packageName, int category) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 49328f1019c3..0071b2f558c4 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -18,9 +18,12 @@ package com.android.server.display; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Resources; import android.os.Environment; import android.os.PowerManager; +import android.util.MathUtils; import android.util.Slog; +import android.util.Spline; import android.view.DisplayAddress; import com.android.internal.R; @@ -72,15 +75,31 @@ public class DisplayDeviceConfig { private final Context mContext; + // Nits and backlight values that are loaded from either the display device config file, or + // config.xml. These are the raw values and just used for the dumpsys + private float[] mRawNits; + private float[] mRawBacklight; + + // These arrays are calculated from the raw arrays, but clamped to contain values equal to and + // between mBacklightMinimum and mBacklightMaximum. These three arrays should all be the same + // length + // Nits array that is used to store the entire range of nits values that the device supports private float[] mNits; + // Backlight array holds the values that the HAL uses to display the corresponding nits values + private float[] mBacklight; + // Purely an array that covers the ranges of values 0.0 - 1.0, indicating the system brightness + // for the corresponding values above private float[] mBrightness; - private float mBrightnessMinimum = Float.NaN; - private float mBrightnessMaximum = Float.NaN; + + private float mBacklightMinimum = Float.NaN; + private float mBacklightMaximum = Float.NaN; private float mBrightnessDefault = Float.NaN; private float mBrightnessRampFastDecrease = Float.NaN; private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private Spline mBrightnessToBacklightSpline; + private Spline mBacklightToBrightnessSpline; private List<String> mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; @@ -167,7 +186,7 @@ public class DisplayDeviceConfig { } /** - * Return the brightness mapping nits array if one is defined in the configuration file. + * Return the brightness mapping nits array. * * @return The brightness mapping nits array. */ @@ -176,22 +195,40 @@ public class DisplayDeviceConfig { } /** - * Return the brightness mapping value array if one is defined in the configuration file. + * Return the brightness mapping backlight array. * - * @return The brightness mapping value array. + * @return The backlight mapping value array. */ - public float[] getBrightness() { - return mBrightness; + public float[] getBacklight() { + return mBacklight; } - public float getBrightnessMinimum() { - return mBrightnessMinimum; + /** + * Calculates the backlight value, as recognised by the HAL, from the brightness value + * given that the rest of the system deals with. + * + * @param brightness value on the framework scale of 0-1 + * @return backlight value on the HAL scale of 0-1 + */ + public float getBacklightFromBrightness(float brightness) { + return mBrightnessToBacklightSpline.interpolate(brightness); } - public float getBrightnessMaximum() { - return mBrightnessMaximum; + /** + * Return an array of equal length to backlight and nits, that covers the entire system + * brightness range of 0.0-1.0. + * + * @return brightness array + */ + public float[] getBrightness() { + return mBrightness; } + /** + * Return the default brightness on a scale of 0.0f - 1.0f + * + * @return default brightness + */ public float getBrightnessDefault() { return mBrightnessDefault; } @@ -237,10 +274,15 @@ public class DisplayDeviceConfig { @Override public String toString() { String str = "DisplayDeviceConfig{" - + "mBrightness=" + Arrays.toString(mBrightness) + + "mBacklight=" + Arrays.toString(mBacklight) + ", mNits=" + Arrays.toString(mNits) - + ", mBrightnessMinimum=" + mBrightnessMinimum - + ", mBrightnessMaximum=" + mBrightnessMaximum + + ", mRawBacklight=" + Arrays.toString(mRawBacklight) + + ", mRawNits=" + Arrays.toString(mRawNits) + + ", mBrightness=" + Arrays.toString(mBrightness) + + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline + + ", mBacklightMinimum=" + mBacklightMinimum + + ", mBacklightMaximum=" + mBacklightMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled @@ -253,10 +295,6 @@ public class DisplayDeviceConfig { return str; } - private float getMaxBrightness() { - return mBrightness[mBrightness.length - 1]; - } - private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, String suffixFormat, long idNumber) { @@ -264,7 +302,6 @@ public class DisplayDeviceConfig { final String filename = String.format(CONFIG_FILE_FORMAT, suffix); final File filePath = Environment.buildPath( baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename); - if (filePath.exists()) { final DisplayDeviceConfig config = new DisplayDeviceConfig(context); config.initFromFile(filePath); @@ -299,9 +336,9 @@ public class DisplayDeviceConfig { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { final DisplayConfiguration config = XmlParser.read(in); if (config != null) { - loadBrightnessMap(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); + loadBrightnessMap(config); loadHighBrightnessModeData(config); loadQuirks(config); loadBrightnessRamps(config); @@ -318,13 +355,20 @@ public class DisplayDeviceConfig { // If no ddc exists, use config.xml loadBrightnessDefaultFromConfigXml(); loadBrightnessConstraintsFromConfigXml(); + loadBrightnessMapFromConfigXml(); loadBrightnessRampsFromConfigXml(); } private void initFromPmValues() { - mBrightnessMinimum = PowerManager.BRIGHTNESS_MIN; - mBrightnessMaximum = PowerManager.BRIGHTNESS_MAX; + // Set all to basic values + mBacklightMinimum = PowerManager.BRIGHTNESS_MIN; + mBacklightMaximum = PowerManager.BRIGHTNESS_MAX; mBrightnessDefault = BRIGHTNESS_DEFAULT; + mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampSlowIncrease = PowerManager.BRIGHTNESS_MAX; + setSimpleMappingStrategyValues(); } private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) { @@ -364,24 +408,27 @@ public class DisplayDeviceConfig { final float max = mContext.getResources().getFloat(com.android.internal.R.dimen .config_screenBrightnessSettingMaximumFloat); if (min == INVALID_BRIGHTNESS_IN_CONFIG || max == INVALID_BRIGHTNESS_IN_CONFIG) { - mBrightnessMinimum = BrightnessSynchronizer.brightnessIntToFloat( + mBacklightMinimum = BrightnessSynchronizer.brightnessIntToFloat( mContext.getResources().getInteger(com.android.internal.R.integer .config_screenBrightnessSettingMinimum)); - mBrightnessMaximum = BrightnessSynchronizer.brightnessIntToFloat( + mBacklightMaximum = BrightnessSynchronizer.brightnessIntToFloat( mContext.getResources().getInteger(com.android.internal.R.integer .config_screenBrightnessSettingMaximum)); } else { - mBrightnessMinimum = min; - mBrightnessMaximum = max; + mBacklightMinimum = min; + mBacklightMaximum = max; } } private void loadBrightnessMap(DisplayConfiguration config) { final NitsMap map = config.getScreenBrightnessMap(); - // Map may not exist in config file + // Map may not exist in display device config if (map == null) { + loadBrightnessMapFromConfigXml(); return; } + + // Use the (preferred) display device config mapping final List<Point> points = map.getPoint(); final int size = points.size(); @@ -408,8 +455,123 @@ public class DisplayDeviceConfig { } ++i; } - mNits = nits; - mBrightness = backlight; + mRawNits = nits; + mRawBacklight = backlight; + constrainNitsAndBacklightArrays(); + } + + private void loadBrightnessMapFromConfigXml() { + // Use the config.xml mapping + final Resources res = mContext.getResources(); + final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray( + com.android.internal.R.array.config_screenBrightnessNits)); + final int[] sysBrightness = res.getIntArray( + com.android.internal.R.array.config_screenBrightnessBacklight); + final float[] sysBrightnessFloat = new float[sysBrightness.length]; + + for (int i = 0; i < sysBrightness.length; i++) { + sysBrightnessFloat[i] = BrightnessSynchronizer.brightnessIntToFloat( + sysBrightness[i]); + } + + // These arrays are allowed to be empty, we set null values so that + // BrightnessMappingStrategy will create a SimpleMappingStrategy instead. + if (sysBrightnessFloat.length == 0 || sysNits.length == 0) { + setSimpleMappingStrategyValues(); + return; + } + + mRawNits = sysNits; + mRawBacklight = sysBrightnessFloat; + constrainNitsAndBacklightArrays(); + } + + private void setSimpleMappingStrategyValues() { + // No translation from backlight to brightness should occur if we are using a + // SimpleMappingStrategy (ie they should be the same) so the splines are + // set to be linear, between 0.0 and 1.0 + mNits = null; + mBacklight = null; + float[] simpleMappingStrategyArray = new float[]{0.0f, 1.0f}; + mBrightnessToBacklightSpline = Spline.createSpline(simpleMappingStrategyArray, + simpleMappingStrategyArray); + mBacklightToBrightnessSpline = Spline.createSpline(simpleMappingStrategyArray, + simpleMappingStrategyArray); + } + + /** + * Change the nits and backlight arrays, so that they cover only the allowed backlight values + * Use the brightness minimum and maximum values to clamp these arrays. + */ + private void constrainNitsAndBacklightArrays() { + if (mRawBacklight[0] > mBacklightMinimum + || mRawBacklight[mRawBacklight.length - 1] < mBacklightMaximum + || mBacklightMinimum > mBacklightMaximum) { + throw new IllegalStateException("Min or max values are invalid" + + "; raw min=" + mRawBacklight[0] + + "; raw max=" + mRawBacklight[mRawBacklight.length - 1] + + "; backlight min=" + mBacklightMinimum + + "; backlight max=" + mBacklightMaximum); + } + + float[] newNits = new float[mRawBacklight.length]; + float[] newBacklight = new float[mRawBacklight.length]; + // Find the starting index of the clamped arrays. This may be less than the min so + // we'll need to clamp this value still when actually doing the remapping. + int newStart = 0; + for (int i = 0; i < mRawBacklight.length - 1; i++) { + if (mRawBacklight[i + 1] > mBacklightMinimum) { + newStart = i; + break; + } + } + + boolean isLastValue = false; + int newIndex = 0; + for (int i = newStart; i < mRawBacklight.length && !isLastValue; i++) { + newIndex = i - newStart; + final float newBacklightVal; + final float newNitsVal; + isLastValue = mRawBacklight[i] > mBacklightMaximum + || i >= mRawBacklight.length - 1; + // Clamp beginning and end to valid backlight values. + if (newIndex == 0) { + newBacklightVal = MathUtils.max(mRawBacklight[i], mBacklightMinimum); + newNitsVal = rawBacklightToNits(i, newBacklightVal); + } else if (isLastValue) { + newBacklightVal = MathUtils.min(mRawBacklight[i], mBacklightMaximum); + newNitsVal = rawBacklightToNits(i - 1, newBacklightVal); + } else { + newBacklightVal = mRawBacklight[i]; + newNitsVal = mRawNits[i]; + } + newBacklight[newIndex] = newBacklightVal; + newNits[newIndex] = newNitsVal; + } + mBacklight = Arrays.copyOf(newBacklight, newIndex + 1); + mNits = Arrays.copyOf(newNits, newIndex + 1); + createBacklightConversionSplines(); + } + + private float rawBacklightToNits(int i, float backlight) { + return MathUtils.map(mRawBacklight[i], mRawBacklight[i + 1], + mRawNits[i], mRawNits[i + 1], backlight); + } + + // This method creates a brightness spline that is of equal length with proportional increments + // to the backlight spline. The values of this array range from 0.0f to 1.0f instead of the + // potential constrained range that the backlight array covers + // These splines are used to convert from the system brightness value to the HAL backlight + // value + private void createBacklightConversionSplines() { + mBrightness = new float[mBacklight.length]; + for (int i = 0; i < mBrightness.length; i++) { + mBrightness[i] = MathUtils.map(mBacklight[0], + mBacklight[mBacklight.length - 1], + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]); + } + mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight); + mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness); } private void loadQuirks(DisplayConfiguration config) { @@ -425,12 +587,14 @@ public class DisplayDeviceConfig { mIsHighBrightnessModeEnabled = hbm.getEnabled(); mHbmData = new HighBrightnessModeData(); mHbmData.minimumLux = hbm.getMinimumLux_all().floatValue(); - mHbmData.transitionPoint = hbm.getTransitionPoint_all().floatValue(); - if (mHbmData.transitionPoint >= getMaxBrightness()) { + float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue(); + if (transitionPointBacklightScale >= mBacklightMaximum) { throw new IllegalArgumentException("HBM transition point invalid. " + mHbmData.transitionPoint + " is not less than " - + getMaxBrightness()); + + mBacklightMaximum); } + mHbmData.transitionPoint = + mBacklightToBrightnessSpline.interpolate(transitionPointBacklightScale); final HbmTiming hbmTiming = hbm.getTiming_all(); mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000; mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 62cf86b31180..86142bc1797d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -190,12 +190,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The dim screen brightness. private final float mScreenBrightnessDimConfig; - // The minimum allowed brightness. - private final float mScreenBrightnessRangeMinimum; - - // The maximum allowed brightness. - private final float mScreenBrightnessRangeMaximum; - private final float mScreenBrightnessDefault; // The minimum allowed brightness while in VR. @@ -443,8 +437,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final Resources resources = context.getResources(); - final float screenBrightnessSettingMinimumFloat = clampAbsoluteBrightness( - pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM)); // DOZE AND DIM SETTINGS mScreenBrightnessDozeConfig = clampAbsoluteBrightness( @@ -453,10 +445,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)); // NORMAL SCREEN SETTINGS - mScreenBrightnessRangeMinimum = - Math.min(screenBrightnessSettingMinimumFloat, mScreenBrightnessDimConfig); - mScreenBrightnessRangeMaximum = clampAbsoluteBrightness( - pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM)); mScreenBrightnessDefault = clampAbsoluteBrightness( mLogicalDisplay.getDisplayInfoLocked().brightnessDefault); @@ -481,18 +469,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayDeviceConfig displayDeviceConfig = logicalDisplay .getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); - // TODO: (b/178183143) Ensure that the ddc is not null - if (displayDeviceConfig != null) { - mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease(); - mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease(); - mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease(); - mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease(); - } else { - mBrightnessRampRateFastDecrease = 1.0f; - mBrightnessRampRateFastIncrease = 1.0f; - mBrightnessRampRateSlowDecrease = 1.0f; - mBrightnessRampRateSlowIncrease = 1.0f; - } + mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease(); + mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease(); + mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease(); + mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease(); mSkipScreenOnBrightnessRamp = resources.getBoolean( com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); @@ -545,12 +525,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.string.config_displayLightSensorType); Sensor lightSensor = findDisplayLightSensor(lightSensorType); - mBrightnessMapper = BrightnessMappingStrategy.create(resources); + final DisplayDeviceConfig ddc = + logicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); + mBrightnessMapper = BrightnessMappingStrategy.create(resources, ddc); if (mBrightnessMapper != null) { mAutomaticBrightnessController = new AutomaticBrightnessController(this, handler.getLooper(), sensorManager, lightSensor, mBrightnessMapper, - lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum, - mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, + lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, logicalDisplay, context); @@ -838,9 +820,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call noteScreenBrightness(mPowerState.getScreenBrightness()); // Initialize all of the brightness tracking state - final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt( - mPowerState.getScreenBrightness())); - if (brightness >= 0.0f) { + final float brightness = convertToNits(mPowerState.getScreenBrightness()); + if (brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); } @@ -1151,10 +1132,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Apply dimming by at least some minimum amount when user activity // timeout is about to expire. if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { - if (brightnessState > mScreenBrightnessRangeMinimum) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { brightnessState = Math.max(Math.min(brightnessState - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT, - mScreenBrightnessDimConfig), mScreenBrightnessRangeMinimum); + mScreenBrightnessDimConfig), PowerManager.BRIGHTNESS_MIN); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); } if (!mAppliedDimming) { @@ -1168,12 +1149,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor // as long as it is above the minimum threshold. if (mPowerRequest.lowPowerMode) { - if (brightnessState > mScreenBrightnessRangeMinimum) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { final float brightnessFactor = Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor); - brightnessState = Math.max(lowPowerBrightnessFloat, - mScreenBrightnessRangeMinimum); + brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER); } if (!mAppliedLowPower) { @@ -1258,9 +1238,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // slider event so notify as if the system changed the brightness. userInitiatedChange = false; } - notifyBrightnessChanged( - BrightnessSynchronizer.brightnessFloatToInt(brightnessState), - userInitiatedChange, hadUserBrightnessPoint); + notifyBrightnessChanged(brightnessState, userInitiatedChange, + hadUserBrightnessPoint); } } @@ -1487,17 +1466,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float clampScreenBrightness(float value) { if (Float.isNaN(value)) { - return mScreenBrightnessRangeMinimum; + return PowerManager.BRIGHTNESS_MIN; } return MathUtils.constrain( - value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); + value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX); } // Checks whether the brightness is within the valid brightness range, not including the off or // invalid states. private boolean isValidBrightnessValue(float brightnessState) { - return brightnessState >= mScreenBrightnessRangeMinimum - && brightnessState <= mScreenBrightnessRangeMaximum; + return brightnessState >= PowerManager.BRIGHTNESS_MIN + && brightnessState <= PowerManager.BRIGHTNESS_MAX; } private void animateScreenBrightness(float target, float rate) { @@ -1874,7 +1853,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessChanged(int brightness, boolean userInitiated, + private void notifyBrightnessChanged(float brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f @@ -1891,9 +1870,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private float convertToNits(int backlight) { + private float convertToNits(float brightness) { if (mBrightnessMapper != null) { - return mBrightnessMapper.convertToNits(backlight); + return mBrightnessMapper.convertToNits(brightness); } else { return -1.0f; } @@ -1972,12 +1951,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(); pw.println("Display Power Controller Configuration:"); - pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); - pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault); pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig); pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); - pw.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault); pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum); pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum); pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault); @@ -2109,10 +2085,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private static int clampAbsoluteBrightness(int value) { - return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); - } - private static float clampAbsoluteBrightness(float value) { return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 4bbf227f7033..3709963b7caa 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -30,7 +30,6 @@ import android.os.Trace; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; -import android.util.Spline; import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; @@ -210,8 +209,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private SurfaceControl.DisplayMode[] mSfDisplayModes; // The active display mode in SurfaceFlinger private SurfaceControl.DisplayMode mActiveSfDisplayMode; - private Spline mSystemBrightnessToNits; - private Spline mNitsToHalBrightness; + private DisplayDeviceConfig mDisplayDeviceConfig; private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides = new DisplayEventReceiver.FrameRateOverride[0]; @@ -410,16 +408,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public DisplayDeviceConfig getDisplayDeviceConfig() { if (mDisplayDeviceConfig == null) { - loadDisplayConfiguration(); + loadDisplayDeviceConfig(); } return mDisplayDeviceConfig; } - private void loadDisplayConfiguration() { - Spline nitsToHal = null; - Spline sysToNits = null; - - // Load the mapping from nits to HAL brightness range (display-device-config.xml) + private void loadDisplayDeviceConfig() { + // Load display device config final Context context = getOverlayContext(); mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, mIsDefaultDisplay); @@ -427,33 +422,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { return; } + // Load brightness HWC quirk mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk( DisplayDeviceConfig.QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC)); - - final float[] halNits = mDisplayDeviceConfig.getNits(); - final float[] halBrightness = mDisplayDeviceConfig.getBrightness(); - if (halNits == null || halBrightness == null) { - return; - } - nitsToHal = Spline.createSpline(halNits, halBrightness); - - // Load the mapping from system brightness range to nits (config.xml) - final Resources res = context.getResources(); - final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)); - final int[] sysBrightness = res.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight); - if (sysNits.length == 0 || sysBrightness.length != sysNits.length) { - return; - } - final float[] sysBrightnessFloat = new float[sysBrightness.length]; - for (int i = 0; i < sysBrightness.length; i++) { - sysBrightnessFloat[i] = sysBrightness[i]; - } - sysToNits = Spline.createSpline(sysBrightnessFloat, sysNits); - - mNitsToHalBrightness = nitsToHal; - mSystemBrightnessToNits = sysToNits; } private boolean updateStaticInfo(SurfaceControl.StaticDisplayInfo info) { @@ -665,15 +636,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { // The display is trusted since it is created by system. mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; - if (mDisplayDeviceConfig != null) { - mInfo.brightnessMinimum = mDisplayDeviceConfig.getBrightnessMinimum(); - mInfo.brightnessMaximum = mDisplayDeviceConfig.getBrightnessMaximum(); - mInfo.brightnessDefault = mDisplayDeviceConfig.getBrightnessDefault(); - } else { - mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; - mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; - mInfo.brightnessDefault = 0.5f; - } + mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; + mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; + mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault(); } return mInfo; } @@ -718,7 +683,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { setDisplayState(Display.STATE_ON); currentState = Display.STATE_ON; } else { - return; // old state and new state is off + if (oldState == Display.STATE_UNKNOWN) { + // There's no guarantee about what the initial state is + // at startup, so we have to set it if previous was UNKNOWN. + setDisplayState(state); + } + return; } } @@ -806,8 +776,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + "id=" + physicalDisplayId + ", brightness=" + brightness + ")"); try { - brightness = displayBrightnessToHalBrightness(brightness); - mBacklightAdapter.setBrightness(brightness); + float backlight = brightnessToBacklight(brightness); + mBacklightAdapter.setBacklight(backlight); Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenBrightness", BrightnessSynchronizer.brightnessFloatToInt(brightness)); @@ -816,35 +786,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } - /** - * Converts brightness range from the framework's brightness space to the - * Hal brightness space if the HAL brightness space has been provided via - * a display device configuration file. - */ - private float displayBrightnessToHalBrightness(float brightness) { - // TODO: b/171380847 - This needs to be deprecated. The nits-to-brightness - // relationship should be specified in display-config OR config.xml, but not - // both, and no nits-space conversion should be necessary. - // - // Only do a conversion if there exists a unique system brightness and a - // unique HAL brightness-to-nits range defined. - if (mSystemBrightnessToNits == null || mNitsToHalBrightness == null) { - return brightness; - } - - // Sys brightness in this conversion is always specified in the old 1-255 - // range, so convert that here before the translation. - final float brightnessInt = - BrightnessSynchronizer.brightnessFloatToIntRange(brightness); - - if (BrightnessSynchronizer.floatEquals( - brightnessInt, PowerManager.BRIGHTNESS_OFF)) { - return PowerManager.BRIGHTNESS_OFF_FLOAT; - } - - final float nits = mSystemBrightnessToNits.interpolate(brightnessInt); - final float halBrightness = mNitsToHalBrightness.interpolate(nits); - return halBrightness; + private float brightnessToBacklight(float brightness) { + return getDisplayDeviceConfig().getBacklightFromBrightness(brightness); } }; } @@ -1333,11 +1276,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } - void setBrightness(float brightness) { + // Set backlight within min and max backlight values + void setBacklight(float backlight) { if (mUseSurfaceControlBrightness || mForceSurfaceControl) { - mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, brightness); + mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight); } else if (mBacklight != null) { - mBacklight.setBrightness(brightness); + mBacklight.setBrightness(backlight); } } diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java index d514aab31e8d..62337c7df03b 100644 --- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java +++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java @@ -46,7 +46,7 @@ import java.util.Set; private static final String ATTR_VALUE = "value"; /* package */ static class Config { - public long lastModifiedDate; + public long lastModifiedMillis; public final Set<String> updatedFontDirs = new ArraySet<>(); public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>(); } @@ -73,7 +73,7 @@ import java.util.Set; } else if (depth == 2) { switch (tag) { case TAG_LAST_MODIFIED_DATE: - out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0); + out.lastModifiedMillis = parseLongAttribute(parser, ATTR_VALUE, 0); break; case TAG_UPDATED_FONT_DIR: out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE)); @@ -101,7 +101,7 @@ import java.util.Set; out.startTag(null, TAG_ROOT); out.startTag(null, TAG_LAST_MODIFIED_DATE); - out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate)); + out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedMillis)); out.endTag(null, TAG_LAST_MODIFIED_DATE); for (String dir : config.updatedFontDirs) { out.startTag(null, TAG_UPDATED_FONT_DIR); 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 08ddc6ddf4ae..86dbe86f85ee 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -112,7 +112,7 @@ final class UpdatableFontDir { private final File mConfigFile; private final File mTmpConfigFile; - private long mLastModifiedDate; + private long mLastModifiedMillis; private int mConfigVersion = 1; /** @@ -147,7 +147,7 @@ final class UpdatableFontDir { /* package */ void loadFontFileMap() { mFontFileInfoMap.clear(); mFontFamilyMap.clear(); - mLastModifiedDate = 0; + mLastModifiedMillis = 0; boolean success = false; try { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); @@ -157,7 +157,7 @@ final class UpdatableFontDir { Slog.e(TAG, "Failed to load config xml file", e); return; } - mLastModifiedDate = config.lastModifiedDate; + mLastModifiedMillis = config.lastModifiedMillis; File[] dirs = mFilesDir.listFiles(); if (dirs == null) return; @@ -200,7 +200,7 @@ final class UpdatableFontDir { if (!success) { mFontFileInfoMap.clear(); mFontFamilyMap.clear(); - mLastModifiedDate = 0; + mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); } } @@ -211,7 +211,7 @@ final class UpdatableFontDir { FileUtils.deleteContents(mFilesDir); mFontFamilyMap.clear(); - mLastModifiedDate = Instant.now().getEpochSecond(); + mLastModifiedMillis = System.currentTimeMillis(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -231,7 +231,7 @@ final class UpdatableFontDir { // Backup the mapping for rollback. ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap); ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap); - long backupLastModifiedDate = mLastModifiedDate; + long backupLastModifiedDate = mLastModifiedMillis; boolean success = false; try { for (FontUpdateRequest request : requests) { @@ -247,7 +247,7 @@ final class UpdatableFontDir { } // Write config file. - mLastModifiedDate = Instant.now().getEpochSecond(); + mLastModifiedMillis = Instant.now().getEpochSecond(); try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -269,7 +269,7 @@ final class UpdatableFontDir { mFontFileInfoMap.putAll(backupMap); mFontFamilyMap.clear(); mFontFamilyMap.putAll(backupFamilies); - mLastModifiedDate = backupLastModifiedDate; + mLastModifiedMillis = backupLastModifiedDate; } } } @@ -527,7 +527,7 @@ final class UpdatableFontDir { private PersistentSystemFontConfig.Config createPersistentConfig() { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = mLastModifiedDate; + config.lastModifiedMillis = mLastModifiedMillis; for (FontFileInfo info : mFontFileInfoMap.values()) { config.updatedFontDirs.add(info.getRandomizedFontDir().getName()); } @@ -560,7 +560,7 @@ final class UpdatableFontDir { // which will be used as a fallback font without being overridden. mergedFamilies.addAll(mFontFamilyMap.values()); return new FontConfig( - mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion); + mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion); } /* package */ int getConfigVersion() { diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index a7f34ed85e0d..4c4c9783fab0 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -259,7 +259,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } private void mayDisableSystemAudioAndARC(int address) { - if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) { + if (!HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) { return; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index f6a79ba1abfb..bbe52bcecea2 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -249,7 +249,12 @@ public class InputManagerService extends IInputManager.Stub new ArrayMap<IBinder, LightSession>(); // State for lid switch + // Lock for the lid switch state. Held when triggering callbacks to guarantee lid switch events + // are delivered in order. For ex, when a new lid switch callback is registered the lock is held + // while the callback is processing the initial lid switch event which guarantees that any + // events that occur at the same time are delivered after the callback has returned. private final Object mLidSwitchLock = new Object(); + @GuardedBy("mLidSwitchLock") private List<LidSwitchCallback> mLidSwitchCallbacks = new ArrayList<>(); // State for the currently installed input filter. @@ -403,9 +408,6 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER; public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE; - /** Indicates an open state for the lid switch. */ - public static final int SW_STATE_LID_OPEN = 0; - /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; @@ -441,13 +443,18 @@ public class InputManagerService extends IInputManager.Stub } void registerLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) { - boolean lidOpen; synchronized (mLidSwitchLock) { mLidSwitchCallbacks.add(callback); - lidOpen = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID) - == SW_STATE_LID_OPEN; + + // Skip triggering the initial callback if the system is not yet ready as the switch + // state will be reported as KEY_STATE_UNKNOWN. The callback will be triggered in + // systemRunning(). + if (mSystemReady) { + boolean lidOpen = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID) + == KEY_STATE_UP; + callback.notifyLidSwitchChanged(0 /* whenNanos */, lidOpen); + } } - callback.notifyLidSwitchChanged(0 /* whenNanos */, lidOpen); } void unregisterLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) { @@ -495,7 +502,18 @@ public class InputManagerService extends IInputManager.Stub } mNotificationManager = (NotificationManager)mContext.getSystemService( Context.NOTIFICATION_SERVICE); - mSystemReady = true; + + synchronized (mLidSwitchLock) { + mSystemReady = true; + + // Send the initial lid switch state to any callback registered before the system was + // ready. + int switchState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID); + for (int i = 0; i < mLidSwitchCallbacks.size(); i++) { + LidSwitchCallback callback = mLidSwitchCallbacks.get(i); + callback.notifyLidSwitchChanged(0 /* whenNanos */, switchState == KEY_STATE_UP); + } + } IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -2540,14 +2558,13 @@ public class InputManagerService extends IInputManager.Stub if ((switchMask & SW_LID_BIT) != 0) { final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0); - - ArrayList<LidSwitchCallback> callbacksCopy; synchronized (mLidSwitchLock) { - callbacksCopy = new ArrayList<>(mLidSwitchCallbacks); - } - for (int i = 0; i < callbacksCopy.size(); i++) { - LidSwitchCallback callbacks = callbacksCopy.get(i); - callbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + if (mSystemReady) { + for (int i = 0; i < mLidSwitchCallbacks.size(); i++) { + LidSwitchCallback callbacks = mLidSwitchCallbacks.get(i); + callbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + } + } } } diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index d08980cfbf24..720be826fd38 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -29,6 +29,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -199,6 +200,8 @@ public class InputShellCommand extends ShellCommand { runRoll(inputSource, displayId); } else if ("motionevent".equals(arg)) { runMotionEvent(inputSource, displayId); + } else if ("keycombination".equals(arg)) { + runKeyCombination(inputSource, displayId); } else { handleDefaultCommands(arg); } @@ -224,7 +227,7 @@ public class InputShellCommand extends ShellCommand { out.println(); out.println("The commands and default sources are:"); out.println(" text <string> (Default: touchscreen)"); - out.println(" keyevent [--longpress] <key code number or name> ..." + out.println(" keyevent [--longpress|--doubletap] <key code number or name> ..." + " (Default: keyboard)"); out.println(" tap <x> <y> (Default: touchscreen)"); out.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]" @@ -234,6 +237,8 @@ public class InputShellCommand extends ShellCommand { out.println(" press (Default: trackball)"); out.println(" roll <dx> <dy> (Default: trackball)"); out.println(" motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)"); + out.println(" keycombination <key code 1> <key code 2> ..." + + " (Default: keyboard)"); } } @@ -282,6 +287,14 @@ public class InputShellCommand extends ShellCommand { final boolean longpress = "--longpress".equals(arg); if (longpress) { arg = getNextArgRequired(); + } else { + final boolean doubleTap = "--doubletap".equals(arg); + if (doubleTap) { + arg = getNextArgRequired(); + final int keycode = KeyEvent.keyCodeFromString(arg); + sendKeyDoubleTap(inputSource, keycode, displayId); + return; + } } do { @@ -292,22 +305,32 @@ public class InputShellCommand extends ShellCommand { private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) { final long now = SystemClock.uptimeMillis(); - int repeatCount = 0; - KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, repeatCount, + KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, inputSource); event.setDisplayId(displayId); injectKeyEvent(event); if (longpress) { - repeatCount++; - injectKeyEvent(KeyEvent.changeTimeRepeat(event, now, repeatCount, + // Some long press behavior would check the event time, we set a new event time here. + final long nextEventTime = now + ViewConfiguration.getGlobalActionKeyTimeout(); + injectKeyEvent(KeyEvent.changeTimeRepeat(event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS)); } injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); } + private void sendKeyDoubleTap(int inputSource, int keyCode, int displayId) { + sendKeyEvent(inputSource, keyCode, false, displayId); + try { + Thread.sleep(ViewConfiguration.getDoubleTapMinTime()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + sendKeyEvent(inputSource, keyCode, false, displayId); + } + private void runTap(int inputSource, int displayId) { inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); sendTap(inputSource, Float.parseFloat(getNextArgRequired()), @@ -440,4 +463,59 @@ public class InputShellCommand extends ShellCommand { final long now = SystemClock.uptimeMillis(); injectMotionEvent(inputSource, action, now, now, x, y, pressure, displayId); } + + private void runKeyCombination(int inputSource, int displayId) { + String arg = getNextArgRequired(); + ArrayList<Integer> keyCodes = new ArrayList<>(); + + while (arg != null) { + final int keyCode = KeyEvent.keyCodeFromString(arg); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + throw new IllegalArgumentException("Unknown keycode: " + arg); + } + keyCodes.add(keyCode); + arg = getNextArg(); + } + + // At least 2 keys. + if (keyCodes.size() < 2) { + throw new IllegalArgumentException("keycombination requires at least 2 keycodes"); + } + + sendKeyCombination(inputSource, keyCodes, displayId); + } + + private void injectKeyEventAsync(KeyEvent event) { + InputManager.getInstance().injectInputEvent(event, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + private void sendKeyCombination(int inputSource, ArrayList<Integer> keyCodes, int displayId) { + final long now = SystemClock.uptimeMillis(); + final int count = keyCodes.size(); + final KeyEvent[] events = new KeyEvent[count]; + for (int i = 0; i < count; i++) { + final KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCodes.get(i), 0, + 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, + inputSource); + event.setDisplayId(displayId); + events[i] = event; + } + + for (KeyEvent event: events) { + // Use async inject so interceptKeyBeforeQueueing or interceptKeyBeforeDispatching could + // handle keys. + injectKeyEventAsync(event); + } + + try { + Thread.sleep(ViewConfiguration.getTapTimeout()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (KeyEvent event: events) { + injectKeyEventAsync(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); + } + } } diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS index 0313a40f7270..82c6ee12c7ae 100644 --- a/services/core/java/com/android/server/input/OWNERS +++ b/services/core/java/com/android/server/input/OWNERS @@ -1,2 +1,3 @@ +lzye@google.com michaelwr@google.com svv@google.com diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 1e6658930840..87e63ebf2651 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5414,37 +5414,36 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult private int onCommandWithSystemIdentity(@Nullable String cmd) { - if ("get-last-switch-user-id".equals(cmd)) { - return mService.getLastSwitchUserId(this); - } - - // For existing "adb shell ime <command>". - if ("ime".equals(cmd)) { - final String imeCommand = getNextArg(); - if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) { - onImeCommandHelp(); - return ShellCommandResult.SUCCESS; - } - switch (imeCommand) { - case "list": - return mService.handleShellCommandListInputMethods(this); - case "enable": - return mService.handleShellCommandEnableDisableInputMethod(this, true); - case "disable": - return mService.handleShellCommandEnableDisableInputMethod(this, false); - case "set": - return mService.handleShellCommandSetInputMethod(this); - case "reset": - return mService.handleShellCommandResetInputMethod(this); - case "tracing": - return mService.handleShellCommandTraceInputMethod(this); - default: - getOutPrintWriter().println("Unknown command: " + imeCommand); - return ShellCommandResult.FAILURE; + switch (TextUtils.emptyIfNull(cmd)) { + case "get-last-switch-user-id": + return mService.getLastSwitchUserId(this); + case "ime": { // For "adb shell ime <command>". + final String imeCommand = TextUtils.emptyIfNull(getNextArg()); + switch (imeCommand) { + case "": + case "-h": + case "help": + return onImeCommandHelp(); + case "list": + return mService.handleShellCommandListInputMethods(this); + case "enable": + return mService.handleShellCommandEnableDisableInputMethod(this, true); + case "disable": + return mService.handleShellCommandEnableDisableInputMethod(this, false); + case "set": + return mService.handleShellCommandSetInputMethod(this); + case "reset": + return mService.handleShellCommandResetInputMethod(this); + case "tracing": + return mService.handleShellCommandTraceInputMethod(this); + default: + getOutPrintWriter().println("Unknown command: " + imeCommand); + return ShellCommandResult.FAILURE; + } } + default: + return handleDefaultCommands(cmd); } - - return handleDefaultCommands(cmd); } @BinderThread @@ -5461,7 +5460,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void onImeCommandHelp() { + @BinderThread + @ShellCommandResult + private int onImeCommandHelp() { try (IndentingPrintWriter pw = new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) { pw.println("ime <command>:"); @@ -5516,6 +5517,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub pw.decreaseIndent(); } + return ShellCommandResult.SUCCESS; } } diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java index 7b400b6e0309..6ea4bd2b1d6d 100644 --- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -17,7 +17,6 @@ package com.android.server.location; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.hardware.location.ActivityRecognitionHardware; import android.hardware.location.IActivityRecognitionHardwareClient; @@ -27,6 +26,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; /** * Proxy class to bind GmsCore to the ActivityRecognitionHardware. @@ -82,7 +82,7 @@ public class HardwareActivityRecognitionProxy { return resolves; } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService service) throws RemoteException { String descriptor = binder.getInterfaceDescriptor(); if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index fd0b9454cfc1..57e9fc9cb719 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -63,6 +63,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -82,6 +83,7 @@ import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Log; @@ -252,9 +254,12 @@ public class LocationManagerService extends ILocationManager.Stub { // @GuardedBy("mProviderManagers") // hold lock for writes, no lock necessary for simple reads - private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = + final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = new CopyOnWriteArrayList<>(); + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mInjector = injector; @@ -284,7 +289,7 @@ public class LocationManagerService extends ILocationManager.Stub { } @Nullable - private LocationProviderManager getLocationProviderManager(String providerName) { + LocationProviderManager getLocationProviderManager(String providerName) { if (providerName == null) { return null; } @@ -319,8 +324,9 @@ public class LocationManagerService extends ILocationManager.Stub { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); manager.startManager(); + manager.setOnProviderLocationTagsChangeListener( + mOnProviderLocationTagsChangeListener); if (realProvider != null) { - // custom logic wrapping all non-passive providers if (manager != mPassiveManager) { boolean enableStationaryThrottling = Settings.Global.getInt( @@ -331,7 +337,6 @@ public class LocationManagerService extends ILocationManager.Stub { mInjector, realProvider, mEventLog); } } - manager.setRealProvider(realProvider); } mProviderManagers.add(manager); @@ -456,8 +461,9 @@ public class LocationManagerService extends ILocationManager.Stub { .setPowerUsage(Integer.parseInt(fragments[8])) .setAccuracy(Integer.parseInt(fragments[9])) .build(); - getOrAddLocationProviderManager(name).setMockProvider( - new MockLocationProvider(properties, CallerIdentity.fromContext(mContext))); + final LocationProviderManager manager = getOrAddLocationProviderManager(name); + manager.setMockProvider(new MockLocationProvider(properties, + CallerIdentity.fromContext(mContext), /*locationTags*/ null)); } } @@ -496,7 +502,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void startGnssBatch(long periodNanos, ILocationListener listener, String packageName, - String attributionTag, String listenerId) { + @Nullable String attributionTag, String listenerId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); if (mGnssManagerService == null) { @@ -633,7 +639,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Nullable @Override public ICancellationSignal getCurrentLocation(String provider, LocationRequest request, - ILocationCallback consumer, String packageName, String attributionTag, + ILocationCallback consumer, String packageName, @Nullable String attributionTag, String listenerId) { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, listenerId); @@ -657,7 +663,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void registerLocationListener(String provider, LocationRequest request, ILocationListener listener, String packageName, @Nullable String attributionTag, - @Nullable String listenerId) { + String listenerId) { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, listenerId); int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), @@ -808,7 +814,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public Location getLastLocation(String provider, LastLocationRequest request, - String packageName, String attributionTag) { + String packageName, @Nullable String attributionTag) { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), identity.getPid()); @@ -875,9 +881,10 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void registerGnssStatusCallback(IGnssStatusListener listener, String packageName, - String attributionTag) { + @Nullable String attributionTag, String listenerId) { if (mGnssManagerService != null) { - mGnssManagerService.registerGnssStatusCallback(listener, packageName, attributionTag); + mGnssManagerService.registerGnssStatusCallback(listener, packageName, attributionTag, + listenerId); } } @@ -890,9 +897,10 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void registerGnssNmeaCallback(IGnssNmeaListener listener, String packageName, - String attributionTag) { + @Nullable String attributionTag, String listenerId) { if (mGnssManagerService != null) { - mGnssManagerService.registerGnssNmeaCallback(listener, packageName, attributionTag); + mGnssManagerService.registerGnssNmeaCallback(listener, packageName, attributionTag, + listenerId); } } @@ -904,11 +912,12 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void addGnssMeasurementsListener(@Nullable GnssMeasurementRequest request, - IGnssMeasurementsListener listener, String packageName, String attributionTag) { + public void addGnssMeasurementsListener(GnssMeasurementRequest request, + IGnssMeasurementsListener listener, String packageName, @Nullable String attributionTag, + String listenerId) { if (mGnssManagerService != null) { mGnssManagerService.addGnssMeasurementsListener(request, listener, packageName, - attributionTag); + attributionTag, listenerId); } } @@ -954,10 +963,10 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener, - String packageName, String attributionTag) { + String packageName, @Nullable String attributionTag, String listenerId) { if (mGnssManagerService != null) { mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, - attributionTag); + attributionTag, listenerId); } } @@ -1144,15 +1153,16 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void addTestProvider(String provider, ProviderProperties properties, - String packageName, String attributionTag) { + List<String> locationTags, String packageName, String attributionTag) { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { return; } - getOrAddLocationProviderManager(provider).setMockProvider( - new MockLocationProvider(properties, identity)); + final LocationProviderManager manager = getOrAddLocationProviderManager(provider); + manager.setMockProvider(new MockLocationProvider(properties, identity, + (locationTags != null) ? new ArraySet<>(locationTags) : null)); } @Override @@ -1285,13 +1295,13 @@ public class LocationManagerService extends ILocationManager.Stub { ipw.println("Historical Aggregate Location Provider Data:"); ipw.increaseIndent(); - ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats = + ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats = mEventLog.copyAggregateStats(); for (int i = 0; i < aggregateStats.size(); i++) { ipw.print(aggregateStats.keyAt(i)); ipw.println(":"); ipw.increaseIndent(); - ArrayMap<String, LocationEventLog.AggregateStats> providerStats = + ArrayMap<CallerIdentity, LocationEventLog.AggregateStats> providerStats = aggregateStats.valueAt(i); for (int j = 0; j < providerStats.size(); j++) { ipw.print(providerStats.keyAt(j)); @@ -1359,7 +1369,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (provider != null && !provider.equals(manager.getName())) { continue; } - if (identity.equalsIgnoringListenerId(manager.getIdentity())) { + if (identity.equals(manager.getIdentity())) { return true; } } @@ -1389,6 +1399,19 @@ public class LocationManagerService extends ILocationManager.Stub { return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); } + + @Override + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + synchronized (mLock) { + mOnProviderLocationTagsChangeListener = listener; + final int providerCount = mProviderManagers.size(); + for (int i = 0; i < providerCount; i++) { + final LocationProviderManager manager = mProviderManagers.get(i); + manager.setOnProviderLocationTagsChangeListener(listener); + } + } + } } private static class SystemInjector implements Injector { diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index f0dd8b559d64..21a9b0442b74 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -75,8 +75,8 @@ class LocationShellCommand extends BasicShellCommandHandler { case "add-test-provider": { String provider = getNextArgRequired(); ProviderProperties properties = parseTestProviderProviderProperties(); - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, /*locationTags*/ null, + mContext.getOpPackageName(), mContext.getFeatureId()); return 0; } case "remove-test-provider": { 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 81c1e45504cb..dde45c4ab52a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -295,7 +295,7 @@ public class ContextHubService extends IContextHubService.Stub { }; SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); manager.addSensorPrivacyListener( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE, listener); + SensorPrivacyManager.Sensors.MICROPHONE, listener); } } @@ -1079,8 +1079,8 @@ public class ContextHubService extends IContextHubService.Stub { */ private void sendMicrophoneDisableSettingUpdate() { SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); - boolean disabled = manager.isIndividualSensorPrivacyEnabled( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE); + boolean disabled = manager.isSensorPrivacyEnabled( + SensorPrivacyManager.Sensors.MICROPHONE); Log.d(TAG, "Mic Disabled Setting: " + disabled); mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled); } diff --git a/services/core/java/com/android/server/location/countrydetector/ComprehensiveCountryDetector.java b/services/core/java/com/android/server/location/countrydetector/ComprehensiveCountryDetector.java index af3907e78432..dda502b2b9aa 100644 --- a/services/core/java/com/android/server/location/countrydetector/ComprehensiveCountryDetector.java +++ b/services/core/java/com/android/server/location/countrydetector/ComprehensiveCountryDetector.java @@ -320,13 +320,15 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { "(source: " + detectedCountry.getSource() + ", countryISO: " + detectedCountry.getCountryIso() + ")") + " isAirplaneModeOff()=" + isAirplaneModeOff() + + " isWifiOn()=" + isWifiOn() + " mListener=" + mListener + " isGeoCoderImplemnted()=" + isGeoCoderImplemented()); } if (startLocationBasedDetection && (detectedCountry == null || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) - && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + && (isAirplaneModeOff() || isWifiOn()) && mListener != null + && isGeoCoderImplemented()) { if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()"); // Start finding location when the source is less reliable than the // location and the airplane mode is off (as geocoder will not @@ -387,6 +389,11 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0; } + protected boolean isWifiOn() { + return Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.WIFI_ON, 0) != 0; + } + /** * Notify the country change. */ 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 dbfd0a52b013..29ce3783c4a1 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -64,16 +64,17 @@ public class LocationEventLog extends LocalEventLog { private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10; @GuardedBy("mAggregateStats") - private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats; + private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats; public LocationEventLog() { super(getLogSize()); mAggregateStats = new ArrayMap<>(4); } - public ArrayMap<String, ArrayMap<String, AggregateStats>> copyAggregateStats() { + /** Copies out all aggregated stats. */ + public ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> copyAggregateStats() { synchronized (mAggregateStats) { - ArrayMap<String, ArrayMap<String, AggregateStats>> copy = new ArrayMap<>( + ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> copy = new ArrayMap<>( mAggregateStats); for (int i = 0; i < copy.size(); i++) { copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i))); @@ -82,17 +83,18 @@ public class LocationEventLog extends LocalEventLog { } } - private AggregateStats getAggregateStats(String provider, String packageName) { + private AggregateStats getAggregateStats(String provider, CallerIdentity identity) { synchronized (mAggregateStats) { - ArrayMap<String, AggregateStats> packageMap = mAggregateStats.get(provider); + ArrayMap<CallerIdentity, AggregateStats> packageMap = mAggregateStats.get(provider); if (packageMap == null) { packageMap = new ArrayMap<>(2); mAggregateStats.put(provider, packageMap); } - AggregateStats stats = packageMap.get(packageName); + CallerIdentity stripped = identity.stripListenerId(); + AggregateStats stats = packageMap.get(stripped); if (stats == null) { stats = new AggregateStats(); - packageMap.put(packageName, stats); + packageMap.put(stripped, stats); } return stats; } @@ -117,34 +119,33 @@ public class LocationEventLog extends LocalEventLog { public void logProviderClientRegistered(String provider, CallerIdentity identity, LocationRequest request) { addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request); - getAggregateStats(provider, identity.getPackageName()) - .markRequestAdded(request.getIntervalMillis()); + getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis()); } /** Logs a client unregistration for a location provider. */ public void logProviderClientUnregistered(String provider, CallerIdentity identity) { addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity); - getAggregateStats(provider, identity.getPackageName()).markRequestRemoved(); + getAggregateStats(provider, identity).markRequestRemoved(); } /** Logs a client for a location provider entering the active state. */ public void logProviderClientActive(String provider, CallerIdentity identity) { - getAggregateStats(provider, identity.getPackageName()).markRequestActive(); + getAggregateStats(provider, identity).markRequestActive(); } /** Logs a client for a location provider leaving the active state. */ public void logProviderClientInactive(String provider, CallerIdentity identity) { - getAggregateStats(provider, identity.getPackageName()).markRequestInactive(); + getAggregateStats(provider, identity).markRequestInactive(); } /** Logs a client for a location provider entering the foreground state. */ public void logProviderClientForeground(String provider, CallerIdentity identity) { - getAggregateStats(provider, identity.getPackageName()).markRequestForeground(); + getAggregateStats(provider, identity).markRequestForeground(); } /** Logs a client for a location provider leaving the foreground state. */ public void logProviderClientBackground(String provider, CallerIdentity identity) { - getAggregateStats(provider, identity.getPackageName()).markRequestBackground(); + getAggregateStats(provider, identity).markRequestBackground(); } /** Logs a change to the provider request for a location provider. */ @@ -165,7 +166,7 @@ public class LocationEventLog extends LocalEventLog { if (Build.IS_DEBUGGABLE || D) { addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity); } - getAggregateStats(provider, identity.getPackageName()).markLocationDelivered(); + getAggregateStats(provider, identity).markLocationDelivered(); } /** Logs that a provider has entered or exited stationary throttling. */ @@ -218,7 +219,7 @@ public class LocationEventLog extends LocalEventLog { protected final String mProvider; - protected ProviderEvent(long timeDelta, String provider) { + ProviderEvent(long timeDelta, String provider) { super(timeDelta); mProvider = provider; } @@ -234,7 +235,7 @@ public class LocationEventLog extends LocalEventLog { private final int mUserId; private final boolean mEnabled; - protected ProviderEnabledEvent(long timeDelta, String provider, int userId, + ProviderEnabledEvent(long timeDelta, String provider, int userId, boolean enabled) { super(timeDelta, provider); mUserId = userId; @@ -252,7 +253,7 @@ public class LocationEventLog extends LocalEventLog { private final boolean mMocked; - protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) { + ProviderMockedEvent(long timeDelta, String provider, boolean mocked) { super(timeDelta, provider); mMocked = mocked; } @@ -273,7 +274,7 @@ public class LocationEventLog extends LocalEventLog { private final CallerIdentity mIdentity; @Nullable private final LocationRequest mLocationRequest; - private ProviderRegisterEvent(long timeDelta, String provider, boolean registered, + ProviderRegisterEvent(long timeDelta, String provider, boolean registered, CallerIdentity identity, @Nullable LocationRequest locationRequest) { super(timeDelta, provider); mRegistered = registered; @@ -296,7 +297,7 @@ public class LocationEventLog extends LocalEventLog { private final ProviderRequest mRequest; - private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) { + ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) { super(timeDelta, provider); mRequest = request; } @@ -311,7 +312,7 @@ public class LocationEventLog extends LocalEventLog { private final int mNumLocations; - private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) { + ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) { super(timeDelta, provider); mNumLocations = numLocations; } @@ -327,7 +328,7 @@ public class LocationEventLog extends LocalEventLog { private final int mNumLocations; @Nullable private final CallerIdentity mIdentity; - private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations, + ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations, @Nullable CallerIdentity identity) { super(timeDelta, provider); mNumLocations = numLocations; @@ -345,7 +346,7 @@ public class LocationEventLog extends LocalEventLog { private final boolean mStationaryThrottled; - private ProviderStationaryThrottledEvent(long timeDelta, String provider, + ProviderStationaryThrottledEvent(long timeDelta, String provider, boolean stationaryThrottled) { super(timeDelta, provider); mStationaryThrottled = stationaryThrottled; @@ -363,7 +364,7 @@ public class LocationEventLog extends LocalEventLog { @LocationPowerSaveMode private final int mLocationPowerSaveMode; - private LocationPowerSaveModeEvent(long timeDelta, + LocationPowerSaveModeEvent(long timeDelta, @LocationPowerSaveMode int locationPowerSaveMode) { super(timeDelta); mLocationPowerSaveMode = locationPowerSaveMode; @@ -401,7 +402,7 @@ public class LocationEventLog extends LocalEventLog { private final int mUserId; private final boolean mEnabled; - private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) { + LocationEnabledEvent(long timeDelta, int userId, boolean enabled) { super(timeDelta); mUserId = userId; mEnabled = enabled; diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 9216a6b245a6..29da177ee4a1 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -371,7 +371,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, Injector injector, GnssNative gnssNative, GnssMetrics gnssMetrics) { - super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES); + super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); mContext = context; mGnssNative = gnssNative; diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index b6695c20bd97..8312c6361835 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -60,7 +60,7 @@ public class GnssManagerService { private static final String ATTRIBUTION_ID = "GnssService"; - private final Context mContext; + final Context mContext; private final GnssNative mGnssNative; private final GnssLocationProvider mGnssLocationProvider; @@ -154,10 +154,11 @@ public class GnssManagerService { * Registers listener for GNSS status changes. */ public void registerGnssStatusCallback(IGnssStatusListener listener, String packageName, - @Nullable String attributionTag) { + @Nullable String attributionTag, String listenerId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, + listenerId); mGnssStatusProvider.addListener(identity, listener); } @@ -172,10 +173,11 @@ public class GnssManagerService { * Registers listener for GNSS NMEA messages. */ public void registerGnssNmeaCallback(IGnssNmeaListener listener, String packageName, - @Nullable String attributionTag) { + @Nullable String attributionTag, String listenerId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, + listenerId); mGnssNmeaProvider.addListener(identity, listener); } @@ -191,12 +193,13 @@ public class GnssManagerService { */ public void addGnssMeasurementsListener(GnssMeasurementRequest request, IGnssMeasurementsListener listener, String packageName, - @Nullable String attributionTag) { + @Nullable String attributionTag, String listenerId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); if (request.isCorrelationVectorOutputsEnabled()) { mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); } - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, + listenerId); mGnssMeasurementsProvider.addListener(request, identity, listener); } @@ -223,10 +226,11 @@ public class GnssManagerService { * Adds a GNSS navigation message listener. */ public void addGnssNavigationMessageListener(IGnssNavigationMessageListener listener, - String packageName, @Nullable String attributionTag) { + String packageName, @Nullable String attributionTag, String listenerId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, + listenerId); mGnssNavigationMessageProvider.addListener(identity, 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 4e0a0b89f238..9ff6e6bc8e32 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -29,6 +29,7 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -67,7 +68,7 @@ public abstract class AbstractLocationProvider { * Default state value for a location provider that is disabled with no properties and an * empty provider package list. */ - public static final State EMPTY_STATE = new State(false, null, null); + public static final State EMPTY_STATE = new State(false, null, null, null); /** * The provider's allowed state. @@ -84,10 +85,14 @@ public abstract class AbstractLocationProvider { */ @Nullable public final CallerIdentity identity; - private State(boolean allowed, ProviderProperties properties, CallerIdentity identity) { + @Nullable public final Set<String> locationTags; + + private State(boolean allowed, ProviderProperties properties, CallerIdentity identity, + Set<String> locationTags) { this.allowed = allowed; this.properties = properties; this.identity = identity; + this.locationTags = locationTags; } /** @@ -97,7 +102,7 @@ public abstract class AbstractLocationProvider { if (allowed == this.allowed) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -108,7 +113,7 @@ public abstract class AbstractLocationProvider { if (Objects.equals(properties, this.properties)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -119,10 +124,22 @@ public abstract class AbstractLocationProvider { if (Objects.equals(identity, this.identity)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); + } + } + + /** + * Returns a state the same as the current but with location tags set as specified. + */ + public State withLocationTags(@Nullable Set<String> locationTags) { + if (Objects.equals(locationTags, this.locationTags)) { + return this; + } else { + return new State(allowed, properties, identity, locationTags); } } + @Override public boolean equals(Object o) { if (this == o) { @@ -133,12 +150,13 @@ public abstract class AbstractLocationProvider { } State state = (State) o; return allowed == state.allowed && properties == state.properties - && Objects.equals(identity, state.identity); + && Objects.equals(identity, state.identity) + && Objects.equals(locationTags, state.locationTags); } @Override public int hashCode() { - return Objects.hash(allowed, properties, identity); + return Objects.hash(allowed, properties, identity, locationTags); } } @@ -195,10 +213,14 @@ public abstract class AbstractLocationProvider { * An optional identity and properties may be provided to initialize the location provider. */ protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity, - @Nullable ProviderProperties properties) { + @Nullable ProviderProperties properties, @Nullable Set<String> locationTags) { + Preconditions.checkArgument(identity == null || identity.getListenerId() == null); mExecutor = executor; mInternalState = new AtomicReference<>(new InternalState(null, - State.EMPTY_STATE.withIdentity(identity).withProperties(properties))); + State.EMPTY_STATE + .withIdentity(identity) + .withProperties(properties).withLocationTags(locationTags)) + ); mController = new Controller(); } @@ -273,10 +295,18 @@ public abstract class AbstractLocationProvider { * Call this method to report a change in provider packages. */ protected void setIdentity(@Nullable CallerIdentity identity) { + Preconditions.checkArgument(identity == null || identity.getListenerId() == null); setState(state -> state.withIdentity(identity)); } /** + * Call this method to report a change in provider location tags. + */ + protected void setLocationTags(@Nullable Set<String> locationTags) { + setState(state -> state.withLocationTags(locationTags)); + } + + /** * Call this method to report a new location. */ protected void reportLocation(LocationResult locationResult) { @@ -335,6 +365,8 @@ public abstract class AbstractLocationProvider { private boolean mStarted = false; + Controller() {} + @Override public State setListener(@Nullable Listener listener) { InternalState oldInternalState = mInternalState.getAndUpdate( diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java index a3ec867220dd..49f6e64a1e0c 100644 --- a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java @@ -40,7 +40,7 @@ class DelegateLocationProvider extends AbstractLocationProvider private boolean mInitialized = false; DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) { - super(executor, null, null); + super(executor, null, null, null); mDelegate = delegate; } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 388b5a4dd54e..1ecf3ee40a53 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -52,6 +52,8 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.LocationTagInfo; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; @@ -85,6 +87,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.LocationPermissions; @@ -117,6 +120,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; @@ -304,6 +308,7 @@ public class LocationProviderManager extends LocationTransport transport, @PermissionLevel int permissionLevel) { super(Objects.requireNonNull(request), identity, transport); + Preconditions.checkArgument(identity.getListenerId() != null); Preconditions.checkArgument(permissionLevel > PERMISSION_NONE); Preconditions.checkArgument(!request.getWorkSource().isEmpty()); @@ -1287,6 +1292,9 @@ public class LocationProviderManager extends @GuardedBy("mLock") private @Nullable OnAlarmListener mDelayedRegister; + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener; + public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog, String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; @@ -1447,6 +1455,19 @@ public class LocationProviderManager extends } } + /** + * Registers a listener for the location tags of the provider. + * + * @param listener The listener + */ + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + Preconditions.checkArgument(mOnLocationTagsChangeListener == null || listener == null); + synchronized (mLock) { + mOnLocationTagsChangeListener = listener; + } + } + public void setMockProvider(@Nullable MockLocationProvider provider) { synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); @@ -2243,6 +2264,27 @@ public class LocationProviderManager extends if (oldState.allowed != newState.allowed) { onEnabledChanged(UserHandle.USER_ALL); } + + if (!Objects.equals(oldState.locationTags, newState.locationTags)) { + if (mOnLocationTagsChangeListener != null) { + if (oldState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + oldState.identity.getUid(), oldState.identity.getPackageName(), + Collections.emptySet()) + )); + } + if (newState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + newState.identity.getUid(), newState.identity.getPackageName(), + newState.locationTags) + )); + } + } + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java index 0d8f64377db0..7660f56f1580 100644 --- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java @@ -28,6 +28,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Set; /** * A mock location provider used by LocationManagerService to implement test providers. @@ -38,9 +39,10 @@ public class MockLocationProvider extends AbstractLocationProvider { @Nullable private Location mLocation; - public MockLocationProvider(ProviderProperties properties, CallerIdentity identity) { + public MockLocationProvider(ProviderProperties properties, CallerIdentity identity, + @Nullable Set<String> locationTags) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, locationTags); } /** Sets the allowed state of this mock provider. */ diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index cb7264e55fa9..4ffa9a509a23 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -75,7 +75,7 @@ public class MockableLocationProvider extends AbstractLocationProvider { public MockableLocationProvider(Object ownerLock) { // using a direct executor is acceptable because all inbound calls are delegated to the // actual provider implementations which will use their own executors - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mOwnerLock = ownerLock; mRequest = ProviderRequest.EMPTY_REQUEST; } diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java index a5758a37b983..ee9d35d21798 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java @@ -47,7 +47,8 @@ public class PassiveLocationProvider extends AbstractLocationProvider { public PassiveLocationProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES); + super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); setAllowed(true); } 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 4c97f645aac9..f00478a3488a 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 @@ -18,6 +18,7 @@ package com.android.server.location.provider.proxy; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -32,10 +33,12 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import com.android.server.location.provider.AbstractLocationProvider; import java.io.FileDescriptor; @@ -49,6 +52,9 @@ import java.util.Objects; */ public class ProxyLocationProvider extends AbstractLocationProvider { + private static final String KEY_LOCATION_TAGS = "android:location_allow_listed_tags"; + private static final String LOCATION_TAGS_SEPARATOR = ";"; + /** * Creates and registers this proxy. If no suitable service is available for the proxy, returns * null. @@ -84,7 +90,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { int nonOverlayPackageResId) { // safe to use direct executor since our locks are not acquired in a code path invoked by // our owning provider - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mContext = context; mServiceWatcher = new ServiceWatcher(context, action, this::onBind, @@ -94,22 +100,34 @@ public class ProxyLocationProvider extends AbstractLocationProvider { mRequest = ProviderRequest.EMPTY_REQUEST; } + private void updateLocationTagInfo(@NonNull BoundService boundService) { + if (boundService.metadata != null) { + final String tagsList = boundService.metadata.getString(KEY_LOCATION_TAGS); + if (tagsList != null) { + final String[] tags = tagsList.split(LOCATION_TAGS_SEPARATOR); + setLocationTags(new ArraySet<>(tags)); + } + } + } + private boolean checkServiceResolves() { return mServiceWatcher.checkServiceResolves(); } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService boundService) throws RemoteException { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); synchronized (mLock) { mProxy = new Proxy(); - mService = service; + mService = boundService.component; provider.setLocationProviderManager(mProxy); ProviderRequest request = mRequest; if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { provider.setRequest(request); } + + updateLocationTagInfo(boundService); } } diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS index 08b8a8c106b7..7577ee5c9fde 100644 --- a/services/core/java/com/android/server/locksettings/OWNERS +++ b/services/core/java/com/android/server/locksettings/OWNERS @@ -1,3 +1,4 @@ jaggies@google.com kchyn@google.com rubinxu@google.com +xunchang@google.com diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 30ea5556b41c..7e00fd69a148 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -194,7 +194,9 @@ class RebootEscrowManager { } public void reportMetric(boolean success) { - FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success); + // TODO(b/179105110) design error code; and report the true value for other fields. + FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, 0, 1, 1, + -1, 0); } public RebootEscrowEventLog getEventLog() { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ecf4438d8aca..2b9dd2d84ac6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -882,9 +882,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); try { + // TODO: There shouldn't be a need to receive callback for all changes. mActivityManager.registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE, - NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android"); + ActivityManager.PROCESS_STATE_UNKNOWN, "android"); mNetworkManager.registerObserver(mAlertObserver); } catch (RemoteException e) { // ignored; both services live in system_server diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 6aefe41891f9..557fa8944445 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -54,6 +54,8 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastDataInput; +import com.android.internal.util.FastDataOutput; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; @@ -89,6 +91,9 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** File header magic number: "ANET" */ private static final int FILE_MAGIC = 0x414E4554; + /** Default buffer size from BufferedInputStream */ + private static final int BUFFER_SIZE = 8192; + private static final int VERSION_NETWORK_INIT = 1; private static final int VERSION_UID_INIT = 1; @@ -434,7 +439,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void read(InputStream in) throws IOException { - read((DataInput) new DataInputStream(in)); + final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE); + read(dataIn); } private void read(DataInput in) throws IOException { @@ -473,8 +479,9 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void write(OutputStream out) throws IOException { - write((DataOutput) new DataOutputStream(out)); - out.flush(); + final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE); + write(dataOut); + dataOut.flush(); } private void write(DataOutput out) throws IOException { diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 160d2daab6a2..afb47e831bdb 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -32,7 +32,7 @@ public interface NotificationDelegate { void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex, Notification.Action action, NotificationVisibility nv, boolean generatedByAssistant); void onNotificationClear(int callingUid, int callingPid, - String pkg, String tag, int id, int userId, String key, + String pkg, int userId, String key, @NotificationStats.DismissalSurface int dismissalSurface, @NotificationStats.DismissalSentiment int dismissalSentiment, NotificationVisibility nv); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8b4c6392fec0..274344a445c3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -64,6 +64,8 @@ import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -1044,15 +1046,19 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationClear(int callingUid, int callingPid, - String pkg, String tag, int id, int userId, String key, + String pkg, int userId, String key, @NotificationStats.DismissalSurface int dismissalSurface, @NotificationStats.DismissalSentiment int dismissalSentiment, NotificationVisibility nv) { + String tag = null; + int id = 0; synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { r.recordDismissalSurface(dismissalSurface); r.recordDismissalSentiment(dismissalSentiment); + tag = r.getSbn().getTag(); + id = r.getSbn().getId(); } } cancelNotification(callingUid, callingPid, pkg, tag, id, 0, @@ -5997,10 +6003,11 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { - am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), + am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), ALLOWLIST_TOKEN, duration, - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED - ); + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_NOTIFICATION_SERVICE, + "NotificationManagerService"); am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index d95a7254efe1..9c4c5101cb6c 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -425,7 +425,7 @@ public final class NativeTombstoneManager { } } stream.end(token); - + break; case (int) Tombstone.SELINUX_LABEL: selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 308e815d7659..9a9b14c31314 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -171,7 +171,7 @@ public class DataLoaderManagerService extends SystemService { } } - private class DataLoaderServiceConnection implements ServiceConnection { + private class DataLoaderServiceConnection implements ServiceConnection, IBinder.DeathRecipient { final int mId; final IDataLoaderStatusListener mListener; IDataLoader mDataLoader; @@ -194,6 +194,13 @@ public class DataLoaderManagerService extends SystemService { mContext.unbindService(this); return; } + try { + service.linkToDeath(this, /*flags=*/0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to DataLoader's death: " + mId, e); + onBindingDied(className); + return; + } callListener(IDataLoaderStatusListener.DATA_LOADER_BOUND); } @@ -218,6 +225,13 @@ public class DataLoaderManagerService extends SystemService { destroy(); } + @Override + public void binderDied() { + Slog.i(TAG, "DataLoader " + mId + " died"); + callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); + destroy(); + } + IDataLoader getDataLoader() { return mDataLoader; } diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 380cdb10569b..6875b8a5abeb 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -56,6 +56,7 @@ public final class DumpState { private boolean mTitlePrinted; private boolean mFullPreferred; + private boolean mCheckIn; private String mTargetPackageName; @@ -118,4 +119,12 @@ public final class DumpState { public void setFullPreferred(boolean fullPreferred) { mFullPreferred = fullPreferred; } + + public boolean isCheckIn() { + return mCheckIn; + } + + public void setCheckIn(boolean checkIn) { + mCheckIn = checkIn; + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 281283048a95..a5e28f164bc2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -530,14 +530,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new SecurityException("User restriction prevents installing"); } - if (params.dataLoaderParams != null - && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("You need the " - + "com.android.permission.USE_INSTALLER_V2 permission " - + "to use a data loader"); - } - // INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK // capability; ensure if this is set as the install reason the app has one of the necessary // signature permissions to perform the rollback. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7bf3c5c1f4c9..460b2f2bf5c6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3547,14 +3547,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public DataLoaderParamsParcel getDataLoaderParams() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null; } @Override public void addFile(int location, String name, long lengthBytes, byte[] metadata, byte[] signature) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); @@ -3587,7 +3585,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void removeFile(int location, String name) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 16966d4de4e6..17aa866199d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1961,9 +1961,10 @@ public class PackageManagerService extends IPackageManager.Stub boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid); boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid); boolean isInstantAppResolutionAllowed(Intent intent, List<ResolveInfo> resolvedActivities, - int userId, boolean skipPackageCheck); + int userId, boolean skipPackageCheck, int flags); boolean isInstantAppResolutionAllowedBody(Intent intent, - List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck); + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck, + int flags); boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, String resolvedType, int flags); boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId); @@ -2322,7 +2323,7 @@ public class PackageManagerService extends IPackageManager.Stub result = filterIfNotSystemUser(mComponentResolver.queryActivities( intent, resolvedType, flags, userId), userId); addInstant = isInstantAppResolutionAllowed(intent, result, userId, - false /*skipPackageCheck*/); + false /*skipPackageCheck*/, flags); // Check for cross profile results. boolean hasNonNegativePriorityResult = hasNonNegativePriority(result); xpResolveInfo = queryCrossProfileIntents( @@ -2387,8 +2388,8 @@ public class PackageManagerService extends IPackageManager.Stub if (result == null || result.size() == 0) { // the caller wants to resolve for a particular package; however, there // were no installed results, so, try to find an ephemeral result - addInstant = isInstantAppResolutionAllowed( - intent, null /*result*/, userId, true /*skipPackageCheck*/); + addInstant = isInstantAppResolutionAllowed(intent, null /*result*/, userId, + true /*skipPackageCheck*/, flags); if (result == null) { result = new ArrayList<>(); } @@ -2618,7 +2619,7 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -2802,7 +2803,7 @@ public class PackageManagerService extends IPackageManager.Stub } result.highestApprovalLevel = Math.max(mDomainVerificationManager - .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId), + .approvalLevelForDomain(ps, intent, flags, riTargetUser.targetUserId), result.highestApprovalLevel); } if (result != null && result.highestApprovalLevel @@ -3049,7 +3050,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = info.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps.getInstantApp(userId)) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app approved for intent; pkg: " @@ -3928,7 +3929,7 @@ public class PackageManagerService extends IPackageManager.Stub public boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { if (mInstantAppResolverConnection == null) { return false; } @@ -3961,14 +3962,14 @@ public class PackageManagerService extends IPackageManager.Stub // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action public boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); for (int n = 0; n < count; n++) { final ResolveInfo info = resolvedActivities.get(n); @@ -3977,7 +3978,7 @@ 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 (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName @@ -4403,6 +4404,7 @@ public class PackageManagerService extends IPackageManager.Stub public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { final String packageName = dumpState.getTargetPackageName(); + final boolean checkin = dumpState.isCheckIn(); switch (type) { case DumpState.DUMP_VERSION: @@ -4415,6 +4417,56 @@ public class PackageManagerService extends IPackageManager.Stub break; } + case DumpState.DUMP_LIBS: + { + boolean printedHeader = false; + final int numSharedLibraries = mSharedLibraries.size(); + for (int index = 0; index < numSharedLibraries; index++) { + final String libName = mSharedLibraries.keyAt(index); + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.get(libName); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); + if (!checkin) { + if (!printedHeader) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Libraries:"); + printedHeader = true; + } + pw.print(" "); + } else { + pw.print("lib,"); + } + pw.print(libraryInfo.getName()); + if (libraryInfo.isStatic()) { + pw.print(" version=" + libraryInfo.getLongVersion()); + } + if (!checkin) { + pw.print(" -> "); + } + if (libraryInfo.getPath() != null) { + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } + pw.print(libraryInfo.getPath()); + } else { + pw.print(" (apk) "); + pw.print(libraryInfo.getPackageName()); + } + pw.println(); + } + } + break; + } + case DumpState.DUMP_PREFERRED_XML: { pw.flush(); @@ -4676,10 +4728,11 @@ public class PackageManagerService extends IPackageManager.Stub } } public boolean isInstantAppResolutionAllowedBody(Intent intent, - List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) { + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck, + int flags) { synchronized (mLock) { return super.isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } } public int getPackageUidInternal(String packageName, int flags, int userId, @@ -9167,6 +9220,11 @@ public class PackageManagerService extends IPackageManager.Stub */ @Override public String[] getPackagesForUid(int uid) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); + enforceCrossUserOrProfilePermission(callingUid, userId, + /* requireFullPermission */ false, + /* checkShell */ false, "getPackagesForUid"); return snapshotComputer().getPackagesForUid(uid); } @@ -9470,20 +9528,20 @@ public class PackageManagerService extends IPackageManager.Stub private boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { return liveComputer().isInstantAppResolutionAllowed( intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action private boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { return liveComputer().isInstantAppResolutionAllowedBody( intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, @@ -9540,7 +9598,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = ri.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps, - intent, userId)) { + intent, flags, userId)) { return ri; } } @@ -9597,8 +9655,9 @@ public class PackageManagerService extends IPackageManager.Stub */ private static boolean hasAnyDomainApproval( @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting, - @NonNull Intent intent, @UserIdInt int userId) { - return manager.approvalLevelForDomain(pkgSetting, intent, userId) + @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags, + @UserIdInt int userId) { + return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; } @@ -22614,7 +22673,7 @@ public class PackageManagerService extends IPackageManager.Stub } final Intent intent = getHomeIntent(); final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null, - PackageManager.GET_META_DATA, userId); + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked( intent, null, 0, resolveInfos, 0, true, false, false, userId); final String packageName = preferredResolveInfo != null @@ -23699,8 +23758,6 @@ public class PackageManagerService extends IPackageManager.Stub if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; DumpState dumpState = new DumpState(); - boolean checkin = false; - ArraySet<String> permissionNames = null; int opti = 0; @@ -23750,7 +23807,7 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(" <package.name>: info about given package"); return; } else if ("--checkin".equals(opt)) { - checkin = true; + dumpState.setCheckIn(true); } else if ("--all-components".equals(opt)) { dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS); } else if ("-f".equals(opt)) { @@ -23904,6 +23961,7 @@ public class PackageManagerService extends IPackageManager.Stub } final String packageName = dumpState.getTargetPackageName(); + final boolean checkin = dumpState.isCheckIn(); if (checkin) { pw.println("vers,1"); } @@ -23992,11 +24050,7 @@ public class PackageManagerService extends IPackageManager.Stub } if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { - // TODO: Move it to ComputerEngine once LongSparseArray<SharedLibraryInfo> is copied - // in snapshot. - synchronized (mLock) { - dumpSharedLibrariesLPr(pw, dumpState, checkin); - } + dump(DumpState.DUMP_LIBS, fd, pw, dumpState); } if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { @@ -24337,53 +24391,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void dumpSharedLibrariesLPr(PrintWriter pw, DumpState dumpState, boolean checkin) { - boolean printedHeader = false; - final int numSharedLibraries = mSharedLibraries.size(); - for (int index = 0; index < numSharedLibraries; index++) { - final String libName = mSharedLibraries.keyAt(index); - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); - if (!checkin) { - if (!printedHeader) { - if (dumpState.onTitlePrinted()) { - pw.println(); - } - pw.println("Libraries:"); - printedHeader = true; - } - pw.print(" "); - } else { - pw.print("lib,"); - } - pw.print(libraryInfo.getName()); - if (libraryInfo.isStatic()) { - pw.print(" version=" + libraryInfo.getLongVersion()); - } - if (!checkin) { - pw.print(" -> "); - } - if (libraryInfo.getPath() != null) { - if (libraryInfo.isNative()) { - pw.print(" (so) "); - } else { - pw.print(" (jar) "); - } - pw.print(libraryInfo.getPath()); - } else { - pw.print(" (apk) "); - pw.print(libraryInfo.getPackageName()); - } - pw.println(); - } - } - } - // ------- apps on sdcard specific code ------- static final boolean DEBUG_SD_INSTALL = false; diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index a83a3f81bc00..38e100e80cd3 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -787,6 +787,10 @@ public abstract class PackageSettingBase extends SettingBase { return firstInstallTime; } + public String getName() { + return name; + } + 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 f1ffdaf7f111..ec7b451c6ec9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -40,7 +40,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.IntentFilterVerificationInfo; -import android.content.pm.overlay.OverlayPaths; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageUserState; @@ -72,6 +71,7 @@ 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; @@ -1028,6 +1028,9 @@ public final class Settings implements Watchable, Snappable { pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath; } pkgSetting.setPath(codePath); + if (IncrementalManager.isIncrementalPath(codePath.getAbsolutePath())) { + pkgSetting.incrementalStates = new IncrementalStates(); + } } // If what we are scanning is a system (and possibly privileged) package, // then make it so, regardless of whether it was previously installed only diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 24c27bedb9f7..c75dd27a383a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -983,7 +983,11 @@ public class UserManagerService extends IUserManager.Stub { @Override public UserInfo getProfileParent(@UserIdInt int userId) { - checkManageUsersPermission("get the profile parent"); + if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException( + "You need MANAGE_USERS or INTERACT_ACROSS_USERS permission to get the " + + "profile parent"); + } synchronized (mUsersLock) { return getProfileParentLU(userId); } @@ -1531,11 +1535,14 @@ public class UserManagerService extends IUserManager.Stub { @Override public String getUserName() { - if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) { - throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED " - + "permissions to: get user name"); + final int callingUid = Binder.getCallingUid(); + if (!hasManageOrCreateUsersPermission() + || hasPermissionGranted( + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) { + throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or " + + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name"); } - final int userId = UserHandle.getUserId(Binder.getCallingUid()); + final int userId = UserHandle.getUserId(callingUid); synchronized (mUsersLock) { UserInfo userInfo = userWithName(getUserInfoLU(userId)); return userInfo == null ? "" : userInfo.name; @@ -3287,7 +3294,7 @@ public class UserManagerService extends IUserManager.Stub { * as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}. */ @Override - public UserInfo createProfileForUserWithThrow(String name, @NonNull String userType, + public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) throws ServiceSpecificException { checkManageOrCreateUsersPermission(flags); @@ -3868,7 +3875,7 @@ public class UserManagerService extends IUserManager.Stub { * @hide */ @Override - public UserInfo createRestrictedProfileWithThrow(String name, int parentUserId) { + public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) { checkManageOrCreateUsersPermission("setupRestrictedProfile"); final UserInfo user = createProfileForUserWithThrow( name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null); diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 051875ce9f68..349561d3f1d1 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -215,7 +215,7 @@ public class DexManager { searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; if (primaryOrSplit && !isUsedByOtherApps - && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) { + && !isPlatformPackage(searchResult.mOwningPackageName)) { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. @@ -232,15 +232,24 @@ public class DexManager { } String classLoaderContext = mapping.getValue(); + + // Overwrite the class loader context for system server (instead of merging it). + // We expect system server jars to only change contexts in between OTAs and to + // otherwise be stable. + // Instead of implementing a complex clear-context logic post OTA, it is much + // simpler to always override the context for system server. This way, the context + // will always be up to date and we will avoid merging which could lead to the + // the context being marked as variable and thus making dexopt non-optimal. + boolean overwriteCLC = isPlatformPackage(searchResult.mOwningPackageName); + if (classLoaderContext != null && VMRuntime.isValidClassLoaderContext(classLoaderContext)) { // Record dex file usage. If the current usage is a new pattern (e.g. new // secondary, or UsedByOtherApps), record will return true and we trigger an // async write to disk to make sure we don't loose the data in case of a reboot. - if (mPackageDexUsage.record(searchResult.mOwningPackageName, dexPath, loaderUserId, loaderIsa, primaryOrSplit, - loadingAppInfo.packageName, classLoaderContext)) { + loadingAppInfo.packageName, classLoaderContext, overwriteCLC)) { mPackageDexUsage.maybeWriteAsync(); } } @@ -474,7 +483,7 @@ public class DexManager { * because they don't need to be compiled).. */ public boolean dexoptSecondaryDex(DexoptOptions options) { - if (PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { + if (isPlatformPackage(options.getPackageName())) { // We could easily redirect to #dexoptSystemServer in this case. But there should be // no-one calling this method directly for system server. // As such we prefer to abort in this case. @@ -534,7 +543,7 @@ public class DexManager { * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded. */ public int dexoptSystemServer(DexoptOptions options) { - if (!PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { + if (!isPlatformPackage(options.getPackageName())) { Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:" + options.getPackageName()); return PackageDexOptimizer.DEX_OPT_FAILED; @@ -662,7 +671,7 @@ public class DexManager { // Special handle system server files. // We don't need an installd call because we have permissions to check if the file // exists. - if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + if (isPlatformPackage(packageName)) { if (!Files.exists(Paths.get(dexPath))) { if (DEBUG) { Slog.w(TAG, "A dex file previously loaded by System Server does not exist " @@ -739,7 +748,8 @@ public class DexManager { boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, dexPath, userId, isa, /*primaryOrSplit*/ false, loadingPackage, - PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, + /*overwriteCLC*/ false); update |= newUpdate; } if (update) { @@ -809,7 +819,7 @@ public class DexManager { // Note: We don't have any way to detect which code paths are actually // owned by system server. We can only assume that such paths are on // system partitions. - if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) { + if (isPlatformPackage(loadingAppInfo.packageName)) { if (isSystemServerDexPathSupportedForOdex(dexPath)) { // We record system server dex files as secondary dex files. // The reason is that we only record the class loader context for secondary dex @@ -842,6 +852,11 @@ public class DexManager { return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); } + /** Returns true if this is the platform package .*/ + private static boolean isPlatformPackage(String packageName) { + return PLATFORM_PACKAGE_NAME.equals(packageName); + } + private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { V existingValue = map.putIfAbsent(key, newValue); return existingValue == null ? newValue : existingValue; diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 10760f52a02b..3d63b75c5da1 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -111,17 +111,18 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * @param dexPath the path of the dex files being loaded * @param ownerUserId the user id which runs the code loading the dex files * @param loaderIsa the ISA of the app loading the dex files - * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates * the file is either primary or a split. False indicates the file is secondary dex. * @param loadingPackageName the package performing the load. Recorded only if it is different * than {@param owningPackageName}. + * @param overwriteCLC if true, the class loader context will be overwritten instead of being + * merged * @return true if the dex load constitutes new information, or false if this information * has been seen before. */ /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, String loaderIsa, boolean primaryOrSplit, - String loadingPackageName, String classLoaderContext) { + String loadingPackageName, String classLoaderContext, boolean overwriteCLC) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); } @@ -193,7 +194,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } // Merge the information into the existing data. // Returns true if there was an update. - return existingData.merge(newData) || updateLoadingPackages; + return existingData.merge(newData, overwriteCLC) || updateLoadingPackages; } } } @@ -809,14 +810,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { mLoadingPackages = new HashSet<>(other.mLoadingPackages); } - private boolean merge(DexUseInfo dexUseInfo) { + private boolean merge(DexUseInfo dexUseInfo, boolean overwriteCLC) { boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); String oldClassLoaderContext = mClassLoaderContext; - if (isUnsupportedContext(mClassLoaderContext)) { + if (overwriteCLC) { + mClassLoaderContext = dexUseInfo.mClassLoaderContext; + } else if (isUnsupportedContext(mClassLoaderContext)) { mClassLoaderContext = dexUseInfo.mClassLoaderContext; } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { // We detected a context change. diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 44a2187aed8e..e3ccb7521b58 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1277,12 +1277,7 @@ final class DefaultPermissionGrantPolicy { newFlags |= (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT); // If we are allowlisting the permission, update the exempt flag before grant. - // If the permission can't be allowlisted by an installer, skip it here because - // this is where the platform takes the role of the installer for exempting - // preinstalled apps. - if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission) - && !pm.getPermissionInfo(permission).isInstallerExemptIgnored()) { - + if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission)) { pm.updatePermissionFlags(permission, pkg, PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, user); diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index 32bee5809b11..ac50f29fbf32 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -239,10 +239,6 @@ public final class Permission { return (mPermissionInfo.flags & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0; } - public boolean isInstallerExemptIgnored() { - return (mPermissionInfo.flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; - } - public boolean isSignature() { return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_SIGNATURE; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 71e53d9f1f40..2dfb6ff3e9a8 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -24,14 +24,12 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; @@ -68,15 +66,10 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; -import android.app.role.RoleManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PermissionGroupInfoFlags; import android.content.pm.PackageManager.PermissionInfoFlags; @@ -86,7 +79,6 @@ import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.UserInfo; import android.content.pm.parsing.component.ParsedPermission; import android.content.pm.parsing.component.ParsedPermissionGroup; import android.content.pm.permission.SplitPermissionInfoParcelable; @@ -401,105 +393,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { new PermissionManagerServiceInternalImpl(); LocalServices.addService(PermissionManagerServiceInternal.class, localService); LocalServices.addService(PermissionManagerInternal.class, localService); - - context.getMainThreadHandler().post(() -> context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - return; - } - - try { - fixBgMicCamera(context); - } catch (Throwable t) { - // Don't crash the system if this fails for any reason. Any intermediate state - // this can leave the permissions in is okay and in the worst case the state is - // the same as before the user rebooted. - Log.e(LOG_TAG, "Unable to fix background permissions", t); - } - } - - - private void fixBgMicCamera(Context context) { - PackageManager pm = context.getPackageManager(); - for (UserInfo userInfo : context.getSystemService(UserManager.class).getUsers()) { - UserHandle user = userInfo.getUserHandle(); - List<String> assistants = context.getSystemService(RoleManager.class) - .getRoleHoldersAsUser(RoleManager.ROLE_ASSISTANT, user); - List<PackageInfo> packages = - pm.getInstalledPackagesAsUser(PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.GET_PERMISSIONS, user.getIdentifier()); - for (PackageInfo packageInfo : packages) { - String[] requestedPermissions = packageInfo.requestedPermissions; - if (requestedPermissions == null) { - continue; - } - for (String permName : requestedPermissions) { - String pkg = packageInfo.packageName; - switch (permName) { - case Manifest.permission.BACKGROUND_CAMERA: - removeFromAllowlistsAndRevoke(pm, pkg, permName, user); - break; - case Manifest.permission.RECORD_BACKGROUND_AUDIO: - if (assistants.contains(pkg)) { - removeFromAllowlistsAndRevokeForAssistant(pm, pkg, permName, - user); - } else { - removeFromAllowlistsAndRevoke(pm, pkg, permName, user); - } - break; - } - } - } - } - } - - private void removeFromAllowlistsAndRevoke(PackageManager pm, String pkg, - String permName, UserHandle user) { - if ((pm.getPermissionFlags(permName, pkg, user) - & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0) { - Slog.i(LOG_TAG, "removing " + pkg + " " + permName + " from all allowlists"); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_UPGRADE); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_SYSTEM); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_INSTALLER); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_ALLOWLIST_ROLE); - } - if (pm.checkPermission(permName, pkg) == PackageManager.PERMISSION_GRANTED) { - Slog.i(LOG_TAG, "revoking " + pkg + " " + permName); - pm.revokeRuntimePermission(pkg, permName, user); - } - } - - private void removeFromAllowlistsAndRevokeForAssistant(PackageManager pm, String pkg, - String permName, UserHandle user) { - int anyNonRoleExempt = - FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT - | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT - | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - - if ((pm.getPermissionFlags(permName, pkg, user) & anyNonRoleExempt) != 0) { - Slog.i(LOG_TAG, "removing " + pkg + " " + permName - + " from all allowlists except role"); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_UPGRADE); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_SYSTEM); - pm.removeWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_WHITELIST_INSTALLER); - } - if ((pm.getPermissionFlags(permName, pkg, user) - & FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT) == 0) { - Slog.i(LOG_TAG, "adding " + pkg + " " + permName - + " to role allowlist"); - pm.addWhitelistedRestrictedPermission(pkg, permName, - FLAG_PERMISSION_ALLOWLIST_ROLE); - } - } - }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED))); } @Override @@ -876,10 +769,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { isRuntimePermission = bp.isRuntime(); - if (bp.isInstallerExemptIgnored()) { - flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - } - final UidPermissionState uidState = getUidStateLocked(pkg, userId); if (uidState == null) { Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); @@ -1123,8 +1012,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); Preconditions.checkArgumentNonNegative(userId, null); if (UserHandle.getCallingUserId() != userId) { @@ -1148,9 +1036,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) { - throw new SecurityException("Querying system or role allowlist requires " + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 + && !isCallerPrivileged) { + throw new SecurityException("Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -1192,9 +1080,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; } - if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) { - queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } ArrayList<String> allowlistedPermissions = null; @@ -1287,8 +1172,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); Preconditions.checkArgument(Integer.bitCount(flags) == 1); Preconditions.checkArgumentNonNegative(userId, null); @@ -1314,10 +1198,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 - && !isCallerPrivileged) { - throw new SecurityException("Modifying system or role allowlist requires " + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) { + throw new SecurityException("Modifying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -3823,15 +3705,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } break; - case FLAG_PERMISSION_ALLOWLIST_ROLE: { - mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - if (permissions != null && permissions.contains(permissionName)) { - newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } else { - newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } - } - break; } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index 080de73ff933..e3cf67c34dad 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -42,6 +42,8 @@ public class DomainVerificationCollector { private static final Pattern DOMAIN_NAME_WITH_WILDCARD = Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern()); + private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024; + @NonNull private final PlatformCompat mPlatformCompat; @@ -71,7 +73,7 @@ public class DomainVerificationCollector { * <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS, * with no other schemes</li> * </ul> - * + * <p> * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly * pretend that all intent filters were set to autoVerify="true". @@ -86,8 +88,8 @@ public class DomainVerificationCollector { } /** - * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires - * {@link IntentFilter#getAutoVerify()} == true. + * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires {@link + * IntentFilter#getAutoVerify()} == true. */ @NonNull public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) { @@ -100,24 +102,21 @@ public class DomainVerificationCollector { boolean restrictDomains = DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS); - ArraySet<String> domains = new ArraySet<>(); - if (restrictDomains) { - collectDomains(domains, pkg, checkAutoVerify); + return collectDomainsInternal(pkg, checkAutoVerify); } else { - collectDomainsLegacy(domains, pkg, checkAutoVerify); + return collectDomainsLegacy(pkg, checkAutoVerify); } - - return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomainsLegacy(@NonNull Set<String> domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { if (!checkAutoVerify) { // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2 - collectDomains(domains, pkg, false); - return; + return collectDomainsInternal(pkg, false); } List<ParsedActivity> activities = pkg.getActivities(); @@ -140,39 +139,54 @@ public class DomainVerificationCollector { } if (!needsAutoVerify) { - return; + return new ArraySet<>(); } } - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + ArraySet<String> domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List<ParsedIntentInfo> intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (intent.handlesWebUris(false)) { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } } + + return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomains(@NonNull Set<String> domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { + ArraySet<String> domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + List<ParsedActivity> activities = pkg.getActivities(); int activitiesSize = activities.size(); - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List<ParsedIntentInfo> intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (checkAutoVerify && !intent.getAutoVerify()) { continue; @@ -198,14 +212,27 @@ public class DomainVerificationCollector { // app developer by declaring a separate intent-filter. This may not be worth // fixing. int authorityCount = intent.countDataAuthorities(); - for (int index = 0; index < authorityCount; index++) { + for (int index = 0; index < authorityCount && underMaxSize; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } + + return domains; + } + + /** + * Ballpark the size of domains to avoid a ridiculous amount of domains that could slow + * down client-server communication. + */ + private int byteSizeOf(String string) { + // Use the same method from core for the data objects so that restrictions are consistent + return android.content.pm.verify.domain.DomainVerificationUtils.estimatedByteSizeOf(string); } /** diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 275dd053fdde..ed37fa0da01f 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -185,6 +185,29 @@ public class DomainVerificationEnforcer { return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } + /** + * Querying for the owners of a domain. Because this API cannot filter the returned list of + * packages, enforces {@link android.Manifest.permission.QUERY_ALL_PACKAGES}, but also enforces + * {@link android.Manifest.permission.INTERACT_ACROSS_USERS} because each user has a different + * state. + */ + public void assertOwnerQuerent(int callingUid, @UserIdInt int callingUserId, + @UserIdInt int targetUserId) { + final int callingPid = Binder.getCallingPid(); + if (callingUserId != targetUserId) { + mContext.enforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS, + callingPid, callingUid, "Caller is not allowed to query other users"); + } + + mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, + callingPid, callingUid, "Caller " + callingUid + " does not hold " + + android.Manifest.permission.QUERY_ALL_PACKAGES); + + mContext.enforcePermission( + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, + callingPid, callingUid, "Caller is not allowed to query user selections"); + } + public interface Callback { /** * @return true if access to the given package should be filtered and the method failed as 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 b6ea901ecd6b..9e22d82910df 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 @@ -310,7 +310,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan */ @ApprovalLevel int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, - @UserIdInt int userId); + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId); /** * @return the domain verification set ID for the given package, or null if the ID is diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index 8aa63372b826..6f2810785c60 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -20,13 +20,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.verify.domain.DomainOwner; +import android.content.pm.verify.domain.DomainSet; +import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationManagerImpl; -import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationUserSelection; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; -import android.util.ArraySet; import java.util.List; import java.util.UUID; @@ -61,11 +62,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationStatus(String domainSetId, List<String> domains, + public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, int state) { try { mService.setDomainVerificationStatus(UUID.fromString(domainSetId), - new ArraySet<>(domains), state); + domainSet.getDomains(), state); } catch (Exception e) { throw rethrow(e); } @@ -82,11 +83,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationUserSelection(String domainSetId, List<String> domains, + public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, boolean enabled, @UserIdInt int userId) { try { mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), - new ArraySet<>(domains), enabled, userId); + domainSet.getDomains(), enabled, userId); } catch (Exception e) { throw rethrow(e); } @@ -103,6 +104,17 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } } + @Nullable + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain, + @UserIdInt int userId) { + try { + return mService.getOwnersForDomain(domain, userId); + } catch (Exception e) { + throw rethrow(e); + } + } + private RuntimeException rethrow(Exception exception) throws RuntimeException { if (exception instanceof InvalidDomainSetException) { int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET; 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 8e5aead05f96..b58c1ff374d5 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 @@ -17,6 +17,7 @@ package com.android.server.pm.verify.domain; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,7 @@ 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.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; @@ -291,6 +293,8 @@ public class DomainVerificationService extends SystemService throws InvalidDomainSetException, NameNotFoundException { mEnforcer.assertApprovedVerifier(callingUid, mProxy); synchronized (mLock) { + List<String> verifiedDomains = new ArrayList<>(); + DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, true /* forAutoVerify */, callingUid, null /* userId */); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); @@ -301,8 +305,17 @@ public class DomainVerificationService extends SystemService continue; } + if (DomainVerificationManager.isStateVerified(state)) { + verifiedDomains.add(domain); + } + stateMap.put(domain, state); } + + int size = verifiedDomains.size(); + for (int index = 0; index < size; index++) { + removeUserSelectionsForDomain(verifiedDomains.get(index)); + } } mConnection.scheduleWriteSettings(); @@ -387,6 +400,20 @@ public class DomainVerificationService extends SystemService } } + private void removeUserSelectionsForDomain(@NonNull String domain) { + synchronized (mLock) { + final int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates(); + int arraySize = array.size(); + for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { + array.valueAt(arrayIndex).removeHost(domain); + } + } + } + } + @Override public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed) throws NameNotFoundException { @@ -470,19 +497,59 @@ public class DomainVerificationService extends SystemService InvalidDomainSetException.REASON_ID_INVALID); } + DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + false /* forAutoVerify */, callingUid, userId); + DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + + // Disable other packages if approving this one. Note that this check is only done for + // enabling. This allows an escape hatch in case multiple packages somehow get selected. + // They can be disabled without blocking in a circular dependency. if (enabled) { + // Cache the approved packages from the 1st pass because the search is expensive + ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>(); + for (String domain : domains) { - if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1, - mConnection::getPackageSettingLocked).first.isEmpty()) { + if (userState.getEnabledHosts().contains(domain)) { + continue; + } + + Pair<List<String>, Integer> packagesToLevel = getApprovedPackages(domain, + userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked); + int highestApproval = packagesToLevel.second; + if (highestApproval > APPROVAL_LEVEL_SELECTION) { throw new InvalidDomainSetException(domainSetId, null, InvalidDomainSetException.REASON_UNABLE_TO_APPROVE); } + + domainToApprovedPackages.put(domain, packagesToLevel.first); + } + + // The removal for other packages must be done in a 2nd pass after it's determined + // that no higher priority owners exist for all of the domains in the set. + int mapSize = domainToApprovedPackages.size(); + for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) { + String domain = domainToApprovedPackages.keyAt(mapIndex); + List<String> approvedPackages = domainToApprovedPackages.valueAt(mapIndex); + int approvedSize = approvedPackages.size(); + for (int approvedIndex = 0; approvedIndex < approvedSize; approvedIndex++) { + String approvedPackage = approvedPackages.get(approvedIndex); + DomainVerificationPkgState approvedPkgState = + mAttachedPkgStates.get(approvedPackage); + if (approvedPkgState == null) { + continue; + } + + DomainVerificationUserState approvedUserState = + approvedPkgState.getUserSelectionState(userId); + if (approvedUserState == null) { + continue; + } + + approvedUserState.removeHost(domain); + } } } - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, - false /* forAutoVerify */, callingUid, userId); - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); if (enabled) { userState.addHosts(domains); } else { @@ -600,28 +667,109 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>(); - - ArraySet<String> domains = mCollector.collectAllWebDomains(pkg); - int domainsSize = domains.size(); - for (int index = 0; index < domainsSize; index++) { - hostToUserSelectionMap.put(domains.valueAt(index), false); - } + ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); + int webDomainsSize = webDomains.size(); + Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); + ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); - boolean linkHandlingAllowed = true; - if (userState != null) { - linkHandlingAllowed = userState.isLinkHandlingAllowed(); - ArraySet<String> enabledHosts = userState.getEnabledHosts(); - int hostsSize = enabledHosts.size(); - for (int index = 0; index < hostsSize; index++) { - hostToUserSelectionMap.put(enabledHosts.valueAt(index), true); + Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts(); + + for (int index = 0; index < webDomainsSize; index++) { + String host = webDomains.valueAt(index); + Integer state = stateMap.get(host); + + int domainState; + if (state != null && DomainVerificationManager.isStateVerified(state)) { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED; + } else if (enabledHosts.contains(host)) { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED; + } else { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE; } + + domains.put(host, domainState); } + boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed(); + return new DomainVerificationUserSelection(pkgState.getId(), packageName, - UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap); + UserHandle.of(userId), linkHandlingAllowed, domains); + } + } + + @NonNull + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + return getOwnersForDomain(domain, mConnection.getCallingUserId()); + } + + public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) { + mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), + userId); + + SparseArray<List<String>> levelToPackages = new SparseArray<>(); + + // First, collect the raw approval level values + 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 = mConnection.getPackageSettingLocked(packageName); + if (pkgSetting == null) { + continue; + } + + int level = approvalLevelForDomain(pkgSetting, domain, userId, domain); + if (level <= APPROVAL_LEVEL_NONE) { + continue; + } + List<String> list = levelToPackages.get(level); + if (list == null) { + list = new ArrayList<>(); + levelToPackages.put(level, list); + } + list.add(packageName); + } + } + + final int size = levelToPackages.size(); + if (size == 0) { + return emptyList(); + } + + // Then sort them ascending by first installed time, with package name as the tie breaker + for (int index = 0; index < size; index++) { + levelToPackages.valueAt(index).sort((first, second) -> { + PackageSetting firstPkgSetting = mConnection.getPackageSettingLocked(first); + PackageSetting secondPkgSetting = mConnection.getPackageSettingLocked(second); + + long firstInstallTime = + firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime(); + long secondInstallTime = + secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime(); + + if (firstInstallTime != secondInstallTime) { + return (int) (firstInstallTime - secondInstallTime); + } + + return first.compareToIgnoreCase(second); + }); } + + List<DomainOwner> owners = new ArrayList<>(); + for (int index = 0; index < size; index++) { + int level = levelToPackages.keyAt(index); + boolean overrideable = level <= APPROVAL_LEVEL_SELECTION; + List<String> packages = levelToPackages.valueAt(index); + int packagesSize = packages.size(); + for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) { + owners.add(new DomainOwner(packages.get(packageIndex), overrideable)); + } + } + + return owners; } @NonNull @@ -634,7 +782,7 @@ public class DomainVerificationService extends SystemService @Override public void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting) { - String pkgName = newPkgSetting.name; + String pkgName = newPkgSetting.getName(); boolean sendBroadcast; synchronized (mLock) { @@ -730,7 +878,7 @@ public class DomainVerificationService extends SystemService // gains or loses all domains. UUID domainSetId = newPkgSetting.getDomainSetId(); - String pkgName = newPkgSetting.name; + String pkgName = newPkgSetting.getName(); boolean sendBroadcast = true; @@ -1346,9 +1494,9 @@ public class DomainVerificationService extends SystemService @Override public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, - @UserIdInt int userId) { - String packageName = pkgSetting.name; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) { + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { + String packageName = pkgSetting.getName(); + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } @@ -1364,7 +1512,7 @@ public class DomainVerificationService extends SystemService */ private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host, @UserIdInt int userId, @NonNull Object debugObject) { - String packageName = pkgSetting.name; + String packageName = pkgSetting.getName(); final AndroidPackage pkg = pkgSetting.getPkg(); // Should never be null, but if it is, skip this and assume that v2 is enabled diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 475d3a87b427..783aff6ccb55 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -40,10 +40,13 @@ public final class DomainVerificationUtils { throw new NameNotFoundException("Package " + packageName + " unavailable"); } - public static boolean isDomainVerificationIntent(Intent intent) { - return intent.isWebIntent() - && intent.hasCategory(Intent.CATEGORY_BROWSABLE) - && intent.hasCategory(Intent.CATEGORY_DEFAULT); + public static boolean isDomainVerificationIntent(Intent intent, int resolveInfoFlags) { + if (!intent.isWebIntent() || !intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { + return false; + } + + return ((resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0) + || intent.hasCategory(Intent.CATEGORY_DEFAULT); } static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg, 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 22468640800e..8fbb33afb6ca 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 @@ -58,6 +58,11 @@ public class DomainVerificationUserState { return this; } + public DomainVerificationUserState removeHost(String host) { + mEnabledHosts.remove(host); + return this; + } + public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java new file mode 100644 index 000000000000..c9653909adb6 --- /dev/null +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -0,0 +1,151 @@ +/* + * 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.policy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal; +import android.location.LocationManagerInternal; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.QuadFunction; +import com.android.server.LocalServices; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class defines policy for special behaviors around app ops. + */ +public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { + @NonNull + private final Object mLock = new Object(); + + /** + * The locking policy around the location tags is a bit special. Since we want to + * avoid grabbing the lock on every op note we are taking the approach where the + * read and write are being done via a thread-safe data structure such that the + * lookup/insert are single thread-safe calls. When we update the cached state we + * use a lock to ensure the update's lookup and store calls are done atomically, + * so multiple writers would not interleave. The tradeoff is we make is that the + * concurrent data structure would use boxing/unboxing of integers but this is + * preferred to locking. + */ + @GuardedBy("mLock - writes only - see above") + @NonNull + private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags = + new ConcurrentHashMap(); + + public AppOpsPolicy() { + final LocationManagerInternal locationManagerInternal = LocalServices.getService( + LocationManagerInternal.class); + locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { + synchronized (mLock) { + final int uid = providerTagInfo.getUid(); + // We make a copy of the per UID state to limit our mutation to one + // operation in the underlying concurrent data structure. + ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + uidTags = new ArrayMap<>(uidTags); + } + + final String packageName = providerTagInfo.getPackageName(); + ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null; + if (packageTags != null) { + packageTags = new ArraySet<>(packageTags); + } + + final Set<String> providerTags = providerTagInfo.getTags(); + if (providerTags != null && !providerTags.isEmpty()) { + if (packageTags != null) { + packageTags.clear(); + packageTags.addAll(providerTags); + } else { + packageTags = new ArraySet<>(providerTags); + } + if (uidTags == null) { + uidTags = new ArrayMap<>(); + } + uidTags.put(packageName, packageTags); + mLocationTags.put(uid, uidTags); + } else if (uidTags != null) { + uidTags.remove(packageName); + if (!uidTags.isEmpty()) { + mLocationTags.put(uid, uidTags); + } else { + mLocationTags.remove(uid); + } + } + } + }); + } + + @Override + public int checkOperation(int code, int uid, String packageName, boolean raw, + QuadFunction<Integer, Integer, String, Boolean, Integer> superImpl) { + return superImpl.apply(code, uid, packageName, raw); + } + + @Override + public int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { + return superImpl.apply(code, usage, uid, packageName); + } + + @Override + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, + Boolean, String, Boolean, Integer> superImpl) { + if (isHandledOp(code)) { + // Only a single lookup from the underlying concurrent data structure + final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + final ArraySet<String> packageTags = uidTags.get(packageName); + if (packageTags != null && packageTags.contains(featureId)) { + return superImpl.apply(resolveLocationOp(code), uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); + } + } + } + return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); + } + + private static boolean isHandledOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + return true; + } + return false; + } + + private static int resolveLocationOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + return AppOpsManager.OP_FINE_LOCATION_SOURCE; + case AppOpsManager.OP_COARSE_LOCATION: + return AppOpsManager.OP_COARSE_LOCATION_SOURCE; + } + return code; + } +} diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java index 84ac12497e71..7f55723cda0b 100644 --- a/services/core/java/com/android/server/policy/KeyCombinationManager.java +++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java @@ -102,9 +102,11 @@ public class KeyCombinationManager { } /** - * Check if the key event could be triggered by combine key rule before dispatching to a window. + * Check if the key event could be intercepted by combination key rule before it is dispatched + * to a window. + * Return true if any active rule could be triggered by the key event, otherwise false. */ - void interceptKey(KeyEvent event, boolean interactive) { + boolean interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); @@ -117,9 +119,9 @@ public class KeyCombinationManager { // exceed time from first key down. forAllRules(mActiveRules, (rule)-> rule.cancel()); mActiveRules.clear(); - return; + return false; } else if (count == 0) { // has some key down but no active rule exist. - return; + return false; } } @@ -127,7 +129,7 @@ public class KeyCombinationManager { mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. - return; + return false; } if (mDownTimes.size() == 1) { @@ -141,7 +143,7 @@ public class KeyCombinationManager { } else { // Ignore if rule already triggered. if (mTriggeredRule != null) { - return; + return true; } // check if second key can trigger rule, or remove the non-match rule. @@ -156,6 +158,7 @@ public class KeyCombinationManager { mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); + return true; } } } else { @@ -168,6 +171,7 @@ public class KeyCombinationManager { } } } + return false; } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4ccd57ddc977..1b192e43c7b8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -73,6 +73,8 @@ import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; @@ -456,7 +458,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mPowerKeyHandled; volatile boolean mBackKeyHandled; volatile boolean mBeganFromNonInteractive; - volatile int mPowerKeyPressCounter; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; volatile boolean mGoingToSleep; @@ -497,7 +498,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mHapticTextHandleEnabled; boolean mUseTvRouting; - int mVeryLongPressTimeout; boolean mAllowStartActivityForLongPressOnPowerDuringSetup; MetricsLogger mLogger; boolean mWakeOnDpadKeyPress; @@ -520,8 +520,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mConsumeSearchKeyUp; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; - int mMetaState; - int mInitialMetaState; // support for activating the lock screen while the screen is on private HashSet<Integer> mAllowLockscreenWhenOnDisplays = new HashSet<>(); @@ -597,14 +595,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0); - private final MutableBoolean mTmpBoolean = new MutableBoolean(false); - private boolean mPerDisplayFocusEnabled = false; private volatile int mTopFocusedDisplayId = INVALID_DISPLAY; private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS; private KeyCombinationManager mKeyCombinationManager; + private SingleKeyGestureDetector mSingleKeyGestureDetector; private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; @@ -615,10 +612,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10; private static final int MSG_HIDE_BOOT_MESSAGE = 11; private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12; - private static final int MSG_POWER_DELAYED_PRESS = 13; - private static final int MSG_POWER_LONG_PRESS = 14; private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15; - private static final int MSG_BACK_LONG_PRESS = 16; private static final int MSG_ACCESSIBILITY_SHORTCUT = 17; private static final int MSG_BUGREPORT_TV = 18; private static final int MSG_ACCESSIBILITY_TV = 19; @@ -626,8 +620,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_SYSTEM_KEY_PRESS = 21; private static final int MSG_HANDLE_ALL_APPS = 22; private static final int MSG_LAUNCH_ASSIST = 23; - private static final int MSG_POWER_VERY_LONG_PRESS = 25; - private static final int MSG_RINGER_TOGGLE_CHORD = 26; + private static final int MSG_RINGER_TOGGLE_CHORD = 24; private class PolicyHandler extends Handler { @Override @@ -668,22 +661,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK: launchVoiceAssistWithWakeLock(); break; - case MSG_POWER_DELAYED_PRESS: - powerPress((Long) msg.obj, msg.arg1 != 0, msg.arg2); - finishPowerKeyPress(); - break; - case MSG_POWER_LONG_PRESS: - powerLongPress((Long) msg.obj /* eventTime */); - break; - case MSG_POWER_VERY_LONG_PRESS: - powerVeryLongPress(); - break; case MSG_SHOW_PICTURE_IN_PICTURE_MENU: showPictureInPictureMenuInternal(); break; - case MSG_BACK_LONG_PRESS: - backLongPress(); - break; case MSG_ACCESSIBILITY_SHORTCUT: accessibilityShortcutActivated(); break; @@ -794,13 +774,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; - private Runnable mPossibleVeryLongPressReboot = new Runnable() { - @Override - public void run() { - mActivityManagerInternal.prepareForPossibleShutdown(); - } - }; - private void handleRingerChordGesture() { if (mRingerToggleChord == VOLUME_HUSH_OFF) { return; @@ -840,28 +813,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void interceptBackKeyDown() { - mLogger.count("key_back_down", 1); - // Reset back key state for long press - mBackKeyHandled = false; - - if (hasLongPressOnBackBehavior()) { - Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - } - } // returns true if the key was handled and should not be passed to the user - private boolean interceptBackKeyUp(KeyEvent event) { - mLogger.count("key_back_up", 1); + private boolean backKeyPress() { + mLogger.count("key_back_press", 1); // Cache handled state boolean handled = mBackKeyHandled; - // Reset back long press state - cancelPendingBackKeyAction(); - if (mHasFeatureWatch) { TelecomManager telecomManager = getTelecommService(); @@ -883,10 +841,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if (mAutofillManagerInternal != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (mAutofillManagerInternal != null) { mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL)); } - return handled; } @@ -896,11 +853,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyWakeLock.acquire(); } - // Cancel multi-press detection timeout. - if (mPowerKeyPressCounter != 0) { - mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); - } - mWindowManagerFuncs.onPowerKeyDown(interactive); // Stop ringing or end call if configured to do so when power is pressed. @@ -922,71 +874,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event); - GestureLauncherService gestureService = LocalServices.getService( - GestureLauncherService.class); - boolean gesturedServiceIntercepted = false; - if (gestureService != null) { - gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, - mTmpBoolean); - if (mTmpBoolean.value && mRequestedOrGoingToSleep) { - mCameraGestureTriggeredDuringGoingToSleep = true; - } - } - // Inform the StatusBar; but do not allow it to consume the event. sendSystemKeyToStatusBarAsync(event.getKeyCode()); - schedulePossibleVeryLongPressReboot(); - // If the power key has still not yet been handled, then detect short // press, long press, or multi press and decide what to do. - mPowerKeyHandled = hungUp || gesturedServiceIntercepted + mPowerKeyHandled = mPowerKeyHandled || hungUp || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); if (!mPowerKeyHandled) { - if (interactive) { - // When interactive, we're already awake. - // Wait for a long press or for the button to be released to decide what to do. - if (hasLongPressOnPowerBehavior()) { - if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - powerLongPress(event.getEventTime()); - } else { - Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS, - event.getEventTime()); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - - if (hasVeryLongPressOnPowerBehavior()) { - Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); - longMsg.setAsynchronous(true); - mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); - } - } - } - } else { + if (!interactive) { wakeUpFromPowerKey(event.getDownTime()); - if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { - if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - powerLongPress(event.getEventTime()); - } else { - Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS, - event.getEventTime()); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - - if (hasVeryLongPressOnPowerBehavior()) { - Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); - longMsg.setAsynchronous(true); - mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); - } - } - mBeganFromNonInteractive = true; } else { final int maxCount = getMaxMultiPressPowerCount(); - if (maxCount <= 1) { mPowerKeyHandled = true; } else { @@ -994,68 +895,38 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } + } else { + // handled by another power key policy. + if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { + mSingleKeyGestureDetector.reset(); + } } } private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { final boolean handled = canceled || mPowerKeyHandled; - cancelPendingPowerKeyAction(); if (!handled) { if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) { // Abort possibly stuck animations only when power key up without long press case. mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); } - - // Figure out how to handle the key now that it has been released. - mPowerKeyPressCounter += 1; - - final int maxCount = getMaxMultiPressPowerCount(); - final long eventTime = event.getDownTime(); - if (mPowerKeyPressCounter < maxCount) { - // This could be a multi-press. Wait a little bit longer to confirm. - // Continue holding the wake lock. - Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS, - interactive ? 1 : 0, mPowerKeyPressCounter, eventTime); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout()); - return; - } - - // No other actions. Handle it immediately. - powerPress(eventTime, interactive, mPowerKeyPressCounter); + } else { + // handled by single key or another power key policy. + mSingleKeyGestureDetector.reset(); + finishPowerKeyPress(); } - // Done. Reset our state. - finishPowerKeyPress(); } private void finishPowerKeyPress() { mBeganFromNonInteractive = false; - mPowerKeyPressCounter = 0; + mPowerKeyHandled = false; if (mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.release(); } } - private void cancelPendingPowerKeyAction() { - if (!mPowerKeyHandled) { - mPowerKeyHandled = true; - mHandler.removeMessages(MSG_POWER_LONG_PRESS); - } - if (hasVeryLongPressOnPowerBehavior()) { - mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); - } - cancelPossibleVeryLongPressReboot(); - } - - private void cancelPendingBackKeyAction() { - if (!mBackKeyHandled) { - mBackKeyHandled = true; - mHandler.removeMessages(MSG_BACK_LONG_PRESS); - } - } - private void powerPress(long eventTime, boolean interactive, int count) { if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " @@ -1206,6 +1077,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void powerLongPress(long eventTime) { final int behavior = getResolvedLongPressOnPowerBehavior(); + switch (behavior) { case LONG_PRESS_POWER_NOTHING: break; @@ -1844,8 +1716,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_triplePressOnPowerBehavior); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); - mVeryLongPressTimeout = mContext.getResources().getInteger( - com.android.internal.R.integer.config_veryLongPressTimeout); mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup); @@ -1939,6 +1809,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }); initKeyCombinationRules(); + initSingleKeyGestureRules(); } private void initKeyCombinationRules() { @@ -1951,7 +1822,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override void execute() { - cancelPendingPowerKeyAction(); + mPowerKeyHandled = true; interceptScreenshotChord(); } @Override @@ -1986,7 +1857,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override void execute() { - cancelPendingPowerKeyAction(); + mPowerKeyHandled = true; interceptRingerToggleChord(); } @Override @@ -2000,7 +1871,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) { @Override void execute() { - cancelPendingBackKeyAction(); + mBackKeyHandled = true; interceptAccessibilityGestureTv(); } @@ -2014,7 +1885,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) { @Override void execute() { - cancelPendingBackKeyAction(); + mBackKeyHandled = true; interceptBugreportGestureTv(); } @@ -2027,6 +1898,84 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** + * Rule for single power key gesture. + */ + private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule { + PowerKeyRule(int gestures) { + super(KEYCODE_POWER, gestures); + } + + @Override + int getMaxMultiPressCount() { + return getMaxMultiPressPowerCount(); + } + + @Override + void onPress(long downTime) { + powerPress(downTime, true, 1 /*count*/); + finishPowerKeyPress(); + } + + @Override + void onLongPress(long downTime) { + powerLongPress(downTime); + } + + @Override + void onVeryLongPress(long downTime) { + mActivityManagerInternal.prepareForPossibleShutdown(); + powerVeryLongPress(); + } + + @Override + void onMultiPress(long downTime, int count) { + powerPress(downTime, true, count); + finishPowerKeyPress(); + } + } + + /** + * Rule for single back key gesture. + */ + private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule { + BackKeyRule(int gestures) { + super(KEYCODE_BACK, gestures); + } + + @Override + int getMaxMultiPressCount() { + return 1; + } + + @Override + void onPress(long downTime) { + mBackKeyHandled |= backKeyPress(); + } + + @Override + void onLongPress(long downTime) { + backLongPress(); + } + } + + private void initSingleKeyGestureRules() { + mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext); + + int powerKeyGestures = 0; + if (hasVeryLongPressOnPowerBehavior()) { + powerKeyGestures |= KEY_VERYLONGPRESS; + } + if (hasLongPressOnPowerBehavior()) { + powerKeyGestures |= KEY_LONGPRESS; + } + mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures)); + + if (hasLongPressOnBackBehavior()) { + mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS)); + } + } + + /** * Read values from config.xml that may be overridden depending on * the configuration of the device. * eg. Disable long press on home goes to recents on sw600dp. @@ -2547,6 +2496,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); final int displayId = event.getDisplayId(); + final long key_consumed = -1; if (DEBUG_INPUT) { Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" @@ -2554,7 +2504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (mKeyCombinationManager.isKeyConsumed(event)) { - return -1; + return key_consumed; } if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { @@ -2575,205 +2525,250 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingCapsLockToggle = false; } - // First we always handle the home key here, so applications - // can never break it, although if keyguard is on, we do let - // it handle it, because that gives us the correct 5 second - // timeout. - if (keyCode == KEYCODE_HOME) { - DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId); - if (handler == null) { - handler = new DisplayHomeButtonHandler(displayId); - mDisplayHomeButtonHandlers.put(displayId, handler); - } - return handler.handleHomeButton(focusedToken, event); - } else if (keyCode == KeyEvent.KEYCODE_MENU) { - // Hijack modified menu keys for debugging features - final int chordBug = KeyEvent.META_SHIFT_ON; - - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return -1; + switch(keyCode) { + case KeyEvent.KEYCODE_HOME: + // First we always handle the home key here, so applications + // can never break it, although if keyguard is on, we do let + // it handle it, because that gives us the correct 5 second + // timeout. + DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId); + if (handler == null) { + handler = new DisplayHomeButtonHandler(displayId); + mDisplayHomeButtonHandlers.put(displayId, handler); } - } - } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - if (down) { - if (repeatCount == 0) { - mSearchKeyShortcutPending = true; - mConsumeSearchKeyUp = false; + return handler.handleHomeButton(focusedToken, event); + case KeyEvent.KEYCODE_MENU: + // Hijack modified menu keys for debugging features + final int chordBug = KeyEvent.META_SHIFT_ON; + + if (down && repeatCount == 0) { + if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return key_consumed; + } } - } else { - mSearchKeyShortcutPending = false; - if (mConsumeSearchKeyUp) { - mConsumeSearchKeyUp = false; - return -1; + break; + case KeyEvent.KEYCODE_SEARCH: + if (down) { + if (repeatCount == 0) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return key_consumed; + } } - } - return 0; - } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) { - if (!keyguardOn) { - if (down && repeatCount == 0) { - preloadRecentApps(); - } else if (!down) { - toggleRecentApps(); + return 0; + case KeyEvent.KEYCODE_APP_SWITCH: + if (!keyguardOn) { + if (down && repeatCount == 0) { + preloadRecentApps(); + } else if (!down) { + toggleRecentApps(); + } } - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_N && event.isMetaPressed()) { - if (down) { - IStatusBarService service = getStatusBarService(); - if (service != null) { - try { - service.expandNotificationsPanel(); - } catch (RemoteException e) { - // do nothing. + return key_consumed; + case KeyEvent.KEYCODE_N: + if (down && event.isMetaPressed()) { + IStatusBarService service = getStatusBarService(); + if (service != null) { + try { + service.expandNotificationsPanel(); + } catch (RemoteException e) { + // do nothing. + } + return key_consumed; } } - } - } else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed() - && event.isCtrlPressed()) { - if (down && repeatCount == 0) { - int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION - : TAKE_SCREENSHOT_FULLSCREEN; - mScreenshotRunnable.setScreenshotType(type); - mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); - mHandler.post(mScreenshotRunnable); - return -1; - } - } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) { - if (down && repeatCount == 0 && !isKeyguardLocked()) { - toggleKeyboardShortcutsMenu(event.getDeviceId()); - } - } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return -1; - } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return -1; - } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { - if (down && repeatCount == 0) { - mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); - mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); - mHandler.post(mScreenshotRunnable); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP - || keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN) { - if (down) { - int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; - - // Disable autobrightness if it's on - int auto = Settings.System.getIntForUser( - mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_MODE, - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, - UserHandle.USER_CURRENT_OR_SELF); - if (auto != 0) { - Settings.System.putIntForUser(mContext.getContentResolver(), + break; + case KeyEvent.KEYCODE_S: + if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION + : TAKE_SCREENSHOT_FULLSCREEN; + mScreenshotRunnable.setScreenshotType(type); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); + mHandler.post(mScreenshotRunnable); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_SLASH: + if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { + toggleKeyboardShortcutsMenu(event.getDeviceId()); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_ASSIST: + Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); + return key_consumed; + case KeyEvent.KEYCODE_VOICE_ASSIST: + Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + + " interceptKeyBeforeQueueing"); + return key_consumed; + case KeyEvent.KEYCODE_SYSRQ: + if (down && repeatCount == 0) { + mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); + mHandler.post(mScreenshotRunnable); + } + return key_consumed; + case KeyEvent.KEYCODE_BRIGHTNESS_UP: + case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: + if (down) { + int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; + + // Disable autobrightness if it's on + int auto = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT_OR_SELF); + if (auto != 0) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, + UserHandle.USER_CURRENT_OR_SELF); + } + float minFloat = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); + float maxFloat = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); + float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction; + float brightnessFloat = Settings.System.getFloatForUser( + mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT, + mContext.getDisplay().getBrightnessDefault(), + UserHandle.USER_CURRENT_OR_SELF); + brightnessFloat += stepFloat; + // Make sure we don't go beyond the limits. + brightnessFloat = Math.min(maxFloat, brightnessFloat); + brightnessFloat = Math.max(minFloat, brightnessFloat); + + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat, + UserHandle.USER_CURRENT_OR_SELF); + startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), + UserHandle.CURRENT_OR_SELF); + } + return key_consumed; + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + if (mUseTvRouting || mHandleVolumeKeysInWM) { + // On TVs or when the configuration is enabled, volume keys never + // go to the foreground app. + dispatchDirectAudioEvent(event); + return key_consumed; } - float minFloat = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float maxFloat = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction; - float brightnessFloat = Settings.System.getFloatForUser( - mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT, - mContext.getDisplay().getBrightnessDefault(), - UserHandle.USER_CURRENT_OR_SELF); - brightnessFloat += stepFloat; - // Make sure we don't go beyond the limits. - brightnessFloat = Math.min(maxFloat, brightnessFloat); - brightnessFloat = Math.max(minFloat, brightnessFloat); - - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat, - UserHandle.USER_CURRENT_OR_SELF); - startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), - UserHandle.CURRENT_OR_SELF); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP - || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN - || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { - if (mUseTvRouting || mHandleVolumeKeysInWM) { - // On TVs or when the configuration is enabled, volume keys never - // go to the foreground app. - dispatchDirectAudioEvent(event); - return -1; - } - // If the device is in VR mode and keys are "internal" (e.g. on the side of the - // device), then drop the volume keys and don't forward it to the application/dispatch - // the audio event. - if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { - final InputDevice d = event.getDevice(); - if (d != null && !d.isExternal()) { - return -1; + // If the device is in VR mode and keys are "internal" (e.g. on the side of the + // device), then drop the volume keys and don't forward it to the + // application/dispatch the audio event. + if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { + final InputDevice d = event.getDevice(); + if (d != null && !d.isExternal()) { + return key_consumed; + } } - } - } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) { - // Pass through keyboard navigation keys. - return 0; - } else if (keyCode == KeyEvent.KEYCODE_ALL_APPS) { - if (!down) { - mHandler.removeMessages(MSG_HANDLE_ALL_APPS); - Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_NOTIFICATION) { - if (!down) { - toggleNotificationPanel(); - } - return -1; - } + break; + case KeyEvent.KEYCODE_TAB: + if (event.isMetaPressed()) { + // Pass through keyboard navigation keys. + return 0; + } + // Display task switcher for ALT-TAB. + if (down && repeatCount == 0) { + if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { + final int shiftlessModifiers = + event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; + if (KeyEvent.metaStateHasModifiers( + shiftlessModifiers, KeyEvent.META_ALT_ON)) { + mRecentAppsHeldModifiers = shiftlessModifiers; + showRecentApps(true); + return key_consumed; + } + } + } + break; + case KeyEvent.KEYCODE_ALL_APPS: + if (!down) { + mHandler.removeMessages(MSG_HANDLE_ALL_APPS); + Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + return key_consumed; + case KeyEvent.KEYCODE_NOTIFICATION: + if (!down) { + toggleNotificationPanel(); + } + return key_consumed; - // Toggle Caps Lock on META-ALT. - boolean actionTriggered = false; - if (KeyEvent.isModifierKey(keyCode)) { - if (!mPendingCapsLockToggle) { - // Start tracking meta state for combo. - mInitialMetaState = mMetaState; - mPendingCapsLockToggle = true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - int altOnMask = mMetaState & KeyEvent.META_ALT_MASK; - int metaOnMask = mMetaState & KeyEvent.META_META_MASK; - - // Check for Caps Lock toggle - if ((metaOnMask != 0) && (altOnMask != 0)) { - // Check if nothing else is pressed - if (mInitialMetaState == (mMetaState ^ (altOnMask | metaOnMask))) { - // Handle Caps Lock Toggle + case KeyEvent.KEYCODE_SPACE: + // Handle keyboard layout switching. + if ((metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) == 0) { + return 0; + } + // Share the same behavior with KEYCODE_LANGUAGE_SWITCH. + case KeyEvent.KEYCODE_LANGUAGE_SWITCH: + if (down && repeatCount == 0) { + int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_META_LEFT: + case KeyEvent.KEYCODE_META_RIGHT: + if (down) { + if (event.isAltPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + mPendingMetaAction = true; + } + } else { + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); - actionTriggered = true; + mPendingCapsLockToggle = false; + } else if (mPendingMetaAction) { + launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, + event.getDeviceId(), + event.getEventTime()); + mPendingMetaAction = false; } } + return key_consumed; + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + if (down) { + if (event.isMetaPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + } + } else { + // hide recent if triggered by ALT-TAB. + if (mRecentAppsHeldModifiers != 0 + && (metaState & mRecentAppsHeldModifiers) == 0) { + mRecentAppsHeldModifiers = 0; + hideRecentApps(true, false); + return key_consumed; + } - // Always stop tracking when key goes up. - mPendingCapsLockToggle = false; - } - } - // Store current meta state to be able to evaluate it later. - mMetaState = metaState; - - if (actionTriggered) { - return -1; - } - - if (KeyEvent.isMetaKey(keyCode)) { - if (down) { - mPendingMetaAction = true; - } else if (mPendingMetaAction) { - launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, event.getDeviceId(), - event.getEventTime()); - } - return -1; + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { + mInputManagerInternal.toggleCapsLock(event.getDeviceId()); + mPendingCapsLockToggle = false; + return key_consumed; + } + } + break; } // Shortcuts are invoked through Search+key, so intercept those here @@ -2803,7 +2798,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); } } - return -1; + return key_consumed; } } @@ -2825,7 +2820,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "the activity to which it is registered was not found: " + "META+" + KeyEvent.keyCodeToString(keyCode), ex); } - return -1; + return key_consumed; } } } @@ -2844,39 +2839,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "the activity to which it is registered was not found: " + "keyCode=" + keyCode + ", category=" + category, ex); } - return -1; - } - } - - // Display task switcher for ALT-TAB. - if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) { - if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { - final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; - if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) { - mRecentAppsHeldModifiers = shiftlessModifiers; - showRecentApps(true); - return -1; - } + return key_consumed; } - } else if (!down && mRecentAppsHeldModifiers != 0 - && (metaState & mRecentAppsHeldModifiers) == 0) { - mRecentAppsHeldModifiers = 0; - hideRecentApps(true, false); - } - - // Handle keyboard language switching. - final boolean isCtrlOrMetaSpace = keyCode == KeyEvent.KEYCODE_SPACE - && (metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) != 0; - if (down && repeatCount == 0 - && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH || isCtrlOrMetaSpace)) { - int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); - return -1; } if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { - return -1; + return key_consumed; } if (down) { @@ -2906,13 +2875,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } catch (RemoteException e) { mShortcutKeyServices.delete(shortcutCode); } - return -1; + return key_consumed; } } // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { - return -1; + return key_consumed; } // Let the application handle the key. @@ -3550,8 +3519,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { return result; } + // Alternate TV power to power key for Android TV device. + final HdmiControlManager hdmiControlManager = getHdmiControlManager(); + if (keyCode == KeyEvent.KEYCODE_TV_POWER && mHasFeatureLeanback + && (hdmiControlManager == null || !hdmiControlManager.shouldHandleTvPowerKey())) { + event = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), KeyEvent.KEYCODE_POWER, + event.getRepeatCount(), event.getMetaState(), + event.getDeviceId(), event.getScanCode(), + event.getFlags(), event.getSource(), event.getDisplayId(), null); + return interceptKeyBeforeQueueing(event, policyFlags); + } + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { - mKeyCombinationManager.interceptKey(event, interactive); + handleKeyGesture(event, interactive); } // Enable haptics if down and virtual key without multiple repetitions. If this is a hard @@ -3566,12 +3548,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { switch (keyCode) { case KeyEvent.KEYCODE_BACK: { if (down) { - interceptBackKeyDown(); + mBackKeyHandled = false; } else { - boolean handled = interceptBackKeyUp(event); - + if (!hasLongPressOnBackBehavior()) { + mBackKeyHandled |= backKeyPress(); + } // Don't pass back press to app if we've already handled it via long press - if (handled) { + if (mBackKeyHandled) { result &= ~ACTION_PASS_TO_USER; } } @@ -3683,33 +3666,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_TV_POWER: { result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately - HdmiControlManager hdmiControlManager = getHdmiControlManager(); - if (hdmiControlManager != null && hdmiControlManager.shouldHandleTvPowerKey()) { - if (down) { - hdmiControlManager.toggleAndFollowTvPower(); - } - } else if (mHasFeatureLeanback) { - KeyEvent fallbackEvent = KeyEvent.obtain( - event.getDownTime(), event.getEventTime(), - event.getAction(), KeyEvent.KEYCODE_POWER, - event.getRepeatCount(), event.getMetaState(), - event.getDeviceId(), event.getScanCode(), - event.getFlags(), event.getSource(), event.getDisplayId(), null); - if (down) { - interceptPowerKeyDown(fallbackEvent, interactive); - } else { - interceptPowerKeyUp(fallbackEvent, interactive, canceled); - } + if (down && hdmiControlManager != null) { + hdmiControlManager.toggleAndFollowTvPower(); } - // Ignore this key for any device that is not connected to a TV via HDMI and - // not an Android TV device. break; } case KeyEvent.KEYCODE_POWER: { EventLogTags.writeInterceptPower( KeyEvent.actionToString(event.getAction()), - mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter); + mPowerKeyHandled ? 1 : 0, + mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER)); // Any activity on the power button stops the accessibility shortcut result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately @@ -3890,6 +3857,43 @@ public class PhoneWindowManager implements WindowManagerPolicy { return result; } + private void handleKeyGesture(KeyEvent event, boolean interactive) { + if (mKeyCombinationManager.interceptKey(event, interactive)) { + // handled by combo keys manager. + mSingleKeyGestureDetector.reset(); + return; + } + + if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) { + mPowerKeyHandled = handleCameraGesture(event, interactive); + if (mPowerKeyHandled) { + // handled by camera gesture. + mSingleKeyGestureDetector.reset(); + return; + } + } + + mSingleKeyGestureDetector.interceptKey(event); + } + + // The camera gesture will be detected by GestureLauncherService. + private boolean handleCameraGesture(KeyEvent event, boolean interactive) { + // camera gesture. + GestureLauncherService gestureService = LocalServices.getService( + GestureLauncherService.class); + if (gestureService == null) { + return false; + } + + final MutableBoolean outLaunched = new MutableBoolean(false); + final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, + interactive, outLaunched); + if (outLaunched.value && mRequestedOrGoingToSleep) { + mCameraGestureTriggeredDuringGoingToSleep = true; + } + return gesturedServiceIntercepted; + } + /** * Handle statusbar expansion events. * @param event @@ -4889,15 +4893,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void schedulePossibleVeryLongPressReboot() { - mHandler.removeCallbacks(mPossibleVeryLongPressReboot); - mHandler.postDelayed(mPossibleVeryLongPressReboot, mVeryLongPressTimeout); - } - - private void cancelPossibleVeryLongPressReboot() { - mHandler.removeCallbacks(mPossibleVeryLongPressReboot); - } - // TODO (multidisplay): Support multiple displays in WindowManagerPolicy. private void updateScreenOffSleepToken(boolean acquire) { if (acquire) { diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java new file mode 100644 index 000000000000..3dafb0ce21ef --- /dev/null +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -0,0 +1,356 @@ +/* + * 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.policy; + +import android.annotation.IntDef; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Detect single key gesture: press, long press, very long press and multi press. + * + * Call {@link #reset} if current {@link KeyEvent} has been handled by another policy + */ + +public final class SingleKeyGestureDetector { + private static final String TAG = "SingleKeyGesture"; + private static final boolean DEBUG = false; + + private static final int MSG_KEY_LONG_PRESS = 0; + private static final int MSG_KEY_VERY_LONG_PRESS = 1; + private static final int MSG_KEY_DELAYED_PRESS = 2; + + private final long mLongPressTimeout; + private final long mVeryLongPressTimeout; + + private volatile int mKeyPressCounter; + + private final ArrayList<SingleKeyRule> mRules = new ArrayList(); + private SingleKeyRule mActiveRule = null; + + // Key code of current key down event, reset when key up. + private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + private volatile boolean mHandledByLongPress = false; + private final Handler mHandler; + private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); + + + /** Supported gesture flags */ + public static final int KEY_LONGPRESS = 1 << 1; + public static final int KEY_VERYLONGPRESS = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "KEY_" }, value = { + KEY_LONGPRESS, + KEY_VERYLONGPRESS, + }) + public @interface KeyGestureFlag {} + + /** + * Rule definition for single keys gesture. + * E.g : define power key. + * <pre class="prettyprint"> + * SingleKeyRule rule = + * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { + * int getMaxMultiPressCount() { // maximum multi press count. } + * void onPress(long downTime) { // short press behavior. } + * void onLongPress() { // long press behavior. } + * void onVeryLongPress() { // very long press behavior. } + * void onMultiPress(long downTime, int count) { // multi press behavior. } + * }; + * </pre> + */ + abstract static class SingleKeyRule { + private final int mKeyCode; + private final int mSupportedGestures; + + SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) { + mKeyCode = keyCode; + mSupportedGestures = supportedGestures; + } + + /** + * True if the rule could intercept the key. + */ + private boolean shouldInterceptKey(int keyCode) { + return keyCode == mKeyCode; + } + + /** + * True if the rule support long press. + */ + private boolean supportLongPress() { + return (mSupportedGestures & KEY_LONGPRESS) != 0; + } + + /** + * True if the rule support very long press. + */ + private boolean supportVeryLongPress() { + return (mSupportedGestures & KEY_VERYLONGPRESS) != 0; + } + + /** + * Maximum count of multi presses. + * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}. + * Otherwise trigger onMultiPress immediately when reach max count when + * {@link KeyEvent.ACTION_DOWN}. + */ + int getMaxMultiPressCount() { + return 1; + } + + /** + * Called when short press has been detected. + */ + abstract void onPress(long downTime); + /** + * Callback when multi press (>= 2) has been detected. + */ + void onMultiPress(long downTime, int count) {} + /** + * Callback when long press has been detected. + */ + void onLongPress(long downTime) {} + /** + * Callback when very long press has been detected. + */ + void onVeryLongPress(long downTime) {} + + @Override + public String toString() { + return "KeyCode = " + KeyEvent.keyCodeToString(mKeyCode) + + ", long press : " + supportLongPress() + + ", very Long press : " + supportVeryLongPress() + + ", max multi press count : " + getMaxMultiPressCount(); + } + } + + public SingleKeyGestureDetector(Context context) { + mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout(); + mVeryLongPressTimeout = context.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout); + mHandler = new KeyHandler(); + } + + void addRule(SingleKeyRule rule) { + mRules.add(rule); + } + + void interceptKey(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + interceptKeyDown(event); + } else { + interceptKeyUp(event); + } + } + + private void interceptKeyDown(KeyEvent event) { + final int keyCode = event.getKeyCode(); + // same key down. + if (mDownKeyCode == keyCode) { + if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 + && !mHandledByLongPress) { + if (DEBUG) { + Log.i(TAG, "Long press Key " + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mActiveRule.onLongPress(event.getEventTime()); + } + return; + } + + // When a different key is pressed, stop processing gestures for the currently active key. + if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN + || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) { + if (DEBUG) { + Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); + } + + reset(); + } + mDownKeyCode = keyCode; + + // Picks a new rule, return if no rule picked. + if (mActiveRule == null) { + final int count = mRules.size(); + for (int index = 0; index < count; index++) { + final SingleKeyRule rule = mRules.get(index); + if (rule.shouldInterceptKey(keyCode)) { + mActiveRule = rule; + break; + } + } + } + if (mActiveRule == null) { + return; + } + + final long eventTime = event.getEventTime(); + if (mKeyPressCounter == 0) { + if (mActiveRule.supportLongPress()) { + final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, + eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, mLongPressTimeout); + } + + if (mActiveRule.supportVeryLongPress()) { + final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, + eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout); + } + } else { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); + + // Trigger multi press immediately when reach max count.( > 1) + if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) { + if (DEBUG) { + Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" + + " reach the max count " + mKeyPressCounter); + } + mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1); + mKeyPressCounter = 0; + } + } + } + + private boolean interceptKeyUp(KeyEvent event) { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + if (mActiveRule == null) { + return false; + } + + if (mHandledByLongPress) { + mHandledByLongPress = false; + return true; + } + + final long downTime = event.getDownTime(); + if (event.getKeyCode() == mActiveRule.mKeyCode) { + // Directly trigger short press when max count is 1. + if (mActiveRule.getMaxMultiPressCount() == 1) { + mActiveRule.onPress(downTime); + return true; + } + + // This could be a multi-press. Wait a little bit longer to confirm. + mKeyPressCounter++; + Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, + mKeyPressCounter, downTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); + return true; + } + reset(); + return false; + } + + int getKeyPressCounter(int keyCode) { + if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) { + return mKeyPressCounter; + } else { + return 0; + } + } + + void reset() { + if (mActiveRule != null) { + if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + } + + if (mKeyPressCounter > 0) { + mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); + mKeyPressCounter = 0; + } + mActiveRule = null; + } + + mHandledByLongPress = false; + mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + } + + boolean isKeyIntercepted(int keyCode) { + if (mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode)) { + return mHandledByLongPress; + } + return false; + } + + private class KeyHandler extends Handler { + KeyHandler() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (mActiveRule == null) { + return; + } + final int keyCode = msg.arg1; + final long eventTime = (long) msg.obj; + switch(msg.what) { + case MSG_KEY_LONG_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mActiveRule.onLongPress(eventTime); + break; + case MSG_KEY_VERY_LONG_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect very long press " + + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mActiveRule.onVeryLongPress(eventTime); + break; + case MSG_KEY_DELAYED_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) + + ", count " + mKeyPressCounter); + } + if (mKeyPressCounter == 1) { + mActiveRule.onPress(eventTime); + } else { + mActiveRule.onMultiPress(eventTime, mKeyPressCounter); + } + mKeyPressCounter = 0; + break; + } + } + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 8c46445fac9b..bc117094dd68 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1645,7 +1645,7 @@ public final class PowerManagerService extends SystemService } // Called from native code. - private void userActivityFromNative(long eventTime, int event, int flags) { + private void userActivityFromNative(long eventTime, int event, int displayId, int flags) { userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID); } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index c4f29ea69218..ef0079e0c01f 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -16,6 +16,8 @@ package com.android.server.powerstats; +import static java.lang.System.currentTimeMillis; + import android.content.Context; import android.hardware.power.stats.Channel; import android.hardware.power.stats.EnergyConsumer; @@ -26,11 +28,14 @@ import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -61,6 +66,8 @@ public final class PowerStatsLogger extends Handler { protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1; protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2; + // TODO(b/181240441): Add a listener to update the Wall clock baseline when changed + private final long mStartWallTime; private final PowerStatsDataStorage mPowerStatsMeterStorage; private final PowerStatsDataStorage mPowerStatsModelStorage; private final PowerStatsDataStorage mPowerStatsResidencyStorage; @@ -79,6 +86,8 @@ public final class PowerStatsLogger extends Handler { // Log power meter data. EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]); + EnergyMeasurementUtils.adjustTimeSinceBootToEpoch(energyMeasurements, + mStartWallTime); mPowerStatsMeterStorage.write( EnergyMeasurementUtils.getProtoBytes(energyMeasurements)); if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements); @@ -86,6 +95,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data without attribution data. EnergyConsumerResult[] ecrNoAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrNoAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false)); if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution); @@ -97,6 +108,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data with attribution data. EnergyConsumerResult[] ecrAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true)); if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution); @@ -108,6 +121,8 @@ public final class PowerStatsLogger extends Handler { // Log state residency data. StateResidencyResult[] stateResidencyResults = mPowerStatsHALWrapper.getStateResidency(new int[0]); + StateResidencyResultUtils.adjustTimeSinceBootToEpoch(stateResidencyResults, + mStartWallTime); mPowerStatsResidencyStorage.write( StateResidencyResultUtils.getProtoBytes(stateResidencyResults)); if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults); @@ -293,12 +308,19 @@ public final class PowerStatsLogger extends Handler { return mDeleteResidencyDataOnBoot; } + @VisibleForTesting + public long getStartWallTime() { + return mStartWallTime; + } + public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); + mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); + if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; mDataStoragePath = dataStoragePath; diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index 11b22a574476..746a09882f93 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -109,6 +109,18 @@ public class ProtoStreamUtils { } static class StateResidencyResultUtils { + public static void adjustTimeSinceBootToEpoch(StateResidencyResult[] stateResidencyResult, + long startWallTime) { + for (int i = 0; i < stateResidencyResult.length; i++) { + final int stateLength = stateResidencyResult[i].stateResidencyData.length; + for (int j = 0; j < stateLength; j++) { + final StateResidency stateResidencyData = + stateResidencyResult[i].stateResidencyData[j]; + stateResidencyData.lastEntryTimestampMs += startWallTime; + } + } + } + public static byte[] getProtoBytes(StateResidencyResult[] stateResidencyResult) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(stateResidencyResult, pos); @@ -306,6 +318,13 @@ public class ProtoStreamUtils { } static class EnergyMeasurementUtils { + public static void adjustTimeSinceBootToEpoch(EnergyMeasurement[] energyMeasurement, + long startWallTime) { + for (int i = 0; i < energyMeasurement.length; i++) { + energyMeasurement[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyMeasurement[] energyMeasurement) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(energyMeasurement, pos); @@ -518,6 +537,13 @@ public class ProtoStreamUtils { } static class EnergyConsumerResultUtils { + public static void adjustTimeSinceBootToEpoch(EnergyConsumerResult[] energyConsumerResult, + long startWallTime) { + for (int i = 0; i < energyConsumerResult.length; i++) { + energyConsumerResult[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult, boolean includeAttribution) { ProtoOutputStream pos = new ProtoOutputStream(); diff --git a/services/core/java/com/android/server/speech/Android.bp b/services/core/java/com/android/server/speech/Android.bp index 379b0754b82a..5605349812da 100644 --- a/services/core/java/com/android/server/speech/Android.bp +++ b/services/core/java/com/android/server/speech/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.speech-sources", srcs: ["java/**/*.java"], diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index a390df9edae1..8ffbb0a87dc0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1336,7 +1336,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onNotificationClear(String pkg, String tag, int id, int userId, String key, + public void onNotificationClear(String pkg, int userId, String key, @NotificationStats.DismissalSurface int dismissalSurface, @NotificationStats.DismissalSentiment int dismissalSentiment, NotificationVisibility nv) { @@ -1345,7 +1345,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId, + mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, userId, key, dismissalSurface, dismissalSentiment, nv); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/timedetector/DeviceConfig.java b/services/core/java/com/android/server/timedetector/DeviceConfig.java deleted file mode 100644 index 7b9ad0f531aa..000000000000 --- a/services/core/java/com/android/server/timedetector/DeviceConfig.java +++ /dev/null @@ -1,126 +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 com.android.server.timedetector; - -import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringDef; - -import java.time.Duration; -import java.util.concurrent.Executor; - -/** - * A helper class for reading / monitoring the {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags. - */ -public final class DeviceConfig { - - /** - * An annotation used to indicate when a {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required. - * - * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider - * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the - * prefix "geotz_" on all of its key strings. - */ - @StringDef(prefix = "KEY_", value = { - KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, - KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, - KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - }) - @interface DeviceConfigKey {} - - /** - * The key to force location time zone detection on for a device. Only intended for use during - * release testing with droidfooders. The user can still disable the feature by turning off the - * master location switch, or disabling automatic time zone detection. - */ - @DeviceConfigKey - public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED = - "force_location_time_zone_detection_enabled"; - - /** - * The key for the default value used to determine whether location time zone detection is - * enabled when the user hasn't explicitly set it yet. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT = - "location_time_zone_detection_enabled_default"; - - /** - * The key for the minimum delay after location time zone detection has been enabled before the - * location time zone manager can report it is uncertain about the time zone. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = - "location_time_zone_detection_uncertainty_delay_millis"; - - /** - * The key for the timeout passed to a location time zone provider that tells it how long it has - * to provide an explicit first suggestion without being declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = - "ltpz_init_timeout_millis"; - - /** - * The key for the extra time added to {@link - * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone - * manager before the location time zone provider will actually be declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = - "ltpz_init_timeout_fuzz_millis"; - - /** Creates an instance. */ - public DeviceConfig() {} - - /** Adds a listener for the system_time namespace. */ - public void addListener( - @NonNull Executor handlerExecutor, @NonNull Runnable listener) { - android.provider.DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_SYSTEM_TIME, - handlerExecutor, - properties -> listener.run()); - } - - /** - * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { - return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); - } - - /** - * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - @Nullable - public Duration getDurationFromMillis( - @DeviceConfigKey String key, @Nullable Duration defaultValue) { - long deviceConfigValue = - android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); - if (deviceConfigValue < 0) { - return defaultValue; - } - return Duration.ofMillis(deviceConfigValue); - } -} diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java new file mode 100644 index 000000000000..8819b371b225 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -0,0 +1,238 @@ +/* + * 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 com.android.server.timedetector; + +import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.provider.DeviceConfig; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.timezonedetector.ConfigurationChangeListener; +import com.android.server.timezonedetector.ServiceConfigAccessor; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace + * for server-configured flags. + */ +public final class ServerFlags { + + private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); + private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); + + /** + * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is + * required. + * + * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider + * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the + * prefix "geotz_" on all of its key strings. + */ + @StringDef(prefix = "KEY_", value = { + KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, + }) + @interface DeviceConfigKey {} + + /** + * Controls whether the location time zone manager service will started. Only observed if + * the device build is configured to support location-based time zone detection. See + * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link + * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = + "location_time_zone_detection_feature_supported"; + + /** + * The key for the server flag that can override the device config for whether the primary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "primary_location_time_zone_provider_enabled_override"; + + /** + * The key for the server flag that can override the device config for whether the secondary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "secondary_location_time_zone_provider_enabled_override"; + + /** + * The key for the minimum delay after location time zone detection has been enabled before the + * location time zone manager can report it is uncertain about the time zone. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = + "location_time_zone_detection_uncertainty_delay_millis"; + + /** + * The key for the timeout passed to a location time zone provider that tells it how long it has + * to provide an explicit first suggestion without being declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = + "ltpz_init_timeout_millis"; + + /** + * The key for the extra time added to {@link + * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone + * manager before the location time zone provider will actually be declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = + "ltpz_init_timeout_fuzz_millis"; + + /** + * The key for the server flag that can override location time zone detection being enabled for + * a user. Only intended for use during release testing with droidfooders. The user can still + * disable the feature by turning off the master location switch, or by disabling automatic time + * zone detection. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = + "location_time_zone_detection_setting_enabled_override"; + + /** + * The key for the default value used to determine whether location time zone detection is + * enabled when the user hasn't explicitly set it yet. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = + "location_time_zone_detection_setting_enabled_default"; + + @GuardedBy("mListeners") + private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); + + private static final Object SLOCK = new Object(); + + @GuardedBy("SLOCK") + @Nullable + private static ServerFlags sInstance; + + private ServerFlags(Context context) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_SYSTEM_TIME, + context.getMainExecutor(), + this::handlePropertiesChanged); + } + + /** Returns the singleton instance. */ + public static ServerFlags getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServerFlags(context); + } + return sInstance; + } + } + + private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { + synchronized (mListeners) { + for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry + : mListeners.entrySet()) { + if (intersects(listenerEntry.getValue(), properties.getKeyset())) { + listenerEntry.getKey().onChange(); + } + } + } + } + + private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) { + for (String toFind : one) { + if (two.contains(toFind)) { + return true; + } + } + return false; + } + + /** + * Adds a listener for the system_time namespace that will trigger if any of the specified keys + * change. Listener callbacks are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no + * associated remove method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener, + @NonNull Set<String> keys) { + Objects.requireNonNull(listener); + Objects.requireNonNull(keys); + + synchronized (mListeners) { + mListeners.put(listener, keys); + } + } + + /** + * Returns an optional boolean value from {@link DeviceConfig} from the system_time + * namespace, returns {@link Optional#empty()} if there is no explicit value set. + */ + @NonNull + public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { + String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); + return parseOptionalBoolean(value); + } + + @NonNull + private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { + if (value == null) { + return Optional.empty(); + } else { + return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; + } + } + + /** + * Returns a boolean value from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { + return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); + } + + /** + * Returns a positive duration from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + @Nullable + public Duration getDurationFromMillis( + @DeviceConfigKey String key, @Nullable Duration defaultValue) { + long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); + if (deviceConfigValue < 0) { + return defaultValue; + } + return Duration.ofMillis(deviceConfigValue); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java index 4c7b1f38dd5a..aa8ad37815bf 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java @@ -17,10 +17,11 @@ package com.android.server.timezonedetector; /** - * A listener used to receive notification that time zone configuration has changed. + * A listener used to receive notification that configuration has / may have changed (depending on + * the usecase). */ @FunctionalInterface public interface ConfigurationChangeListener { - /** Called when the current user or a configuration value has changed. */ + /** Called when the configuration may have changed. */ void onChange(); } diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 1f73977444f8..3ae9d641e81c 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -33,26 +33,27 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** - * Holds all configuration values that affect time zone behavior and some associated logic, e.g. - * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link - * #createCapabilitiesAndConfig()}. + * Holds configuration values that affect user-facing time zone behavior and some associated logic. + * Some configuration is global, some is user scoped, but this class deliberately doesn't make a + * distinction for simplicity. */ public final class ConfigurationInternal { - private final @UserIdInt int mUserId; - private final boolean mUserConfigAllowed; private final boolean mAutoDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; + private final @UserIdInt int mUserId; + private final boolean mUserConfigAllowed; private final boolean mLocationEnabled; private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { - mUserId = builder.mUserId; - mUserConfigAllowed = builder.mUserConfigAllowed; mAutoDetectionSupported = builder.mAutoDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + + mUserId = builder.mUserId; + mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported @@ -60,22 +61,6 @@ public final class ConfigurationInternal { Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } - /** Returns the ID of the user this configuration is associated with. */ - public @UserIdInt int getUserId() { - return mUserId; - } - - /** Returns the handle of the user this configuration is associated with. */ - @NonNull - public UserHandle getUserHandle() { - return UserHandle.of(mUserId); - } - - /** Returns true if the user allowed to modify time zone configuration. */ - public boolean isUserConfigAllowed() { - return mUserConfigAllowed; - } - /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { return mAutoDetectionSupported; @@ -98,6 +83,22 @@ public final class ConfigurationInternal { return mAutoDetectionSupported && mAutoDetectionEnabled; } + /** Returns the ID of the user this configuration is associated with. */ + public @UserIdInt int getUserId() { + return mUserId; + } + + /** Returns the handle of the user this configuration is associated with. */ + @NonNull + public UserHandle getUserHandle() { + return UserHandle.of(mUserId); + } + + /** Returns true if the user allowed to modify time zone configuration. */ + public boolean isUserConfigAllowed() { + return mUserConfigAllowed; + } + /** Returns true if user's location can be used generally. */ public boolean isLocationEnabled() { return mLocationEnabled; @@ -283,7 +284,7 @@ public final class ConfigurationInternal { /** * Sets whether any form of automatic time zone detection is supported on this device. */ - public Builder setAutoDetectionSupported(boolean supported) { + public Builder setAutoDetectionFeatureSupported(boolean supported) { mAutoDetectionSupported = supported; return this; } @@ -291,7 +292,7 @@ public final class ConfigurationInternal { /** * Sets whether geolocation time zone detection is supported on this device. */ - public Builder setGeoDetectionSupported(boolean supported) { + public Builder setGeoDetectionFeatureSupported(boolean supported) { mGeoDetectionSupported = supported; return this; } diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index f52b9b1b1c58..e3caae9482d9 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -31,9 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.location.LocationManager; -import android.net.ConnectivityManager; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -42,10 +40,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import java.util.Objects; -import java.util.concurrent.Executor; +import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. @@ -59,8 +56,7 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @NonNull private final Handler mHandler; @NonNull private final ContentResolver mCr; @NonNull private final UserManager mUserManager; - @NonNull private final DeviceConfig mDeviceConfig; - @NonNull private final boolean mGeoDetectionSupported; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final LocationManager mLocationManager; // @NonNull after setConfigChangeListener() is called. @@ -68,17 +64,16 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, - @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); - Executor handlerExecutor = new HandlerExecutor(mHandler); mCr = context.getContentResolver(); mUserManager = context.getSystemService(UserManager.class); mLocationManager = context.getSystemService(LocationManager.class); - mDeviceConfig = deviceConfig; - mGeoDetectionSupported = geoDetectionSupported; + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); - // Wire up the change listeners. All invocations are performed on the mHandler thread. + // Wire up the config change listeners. All invocations are performed on the mHandler + // thread. // Listen for the user changing / the user's location mode changing. IntentFilter filter = new IntentFilter(); @@ -112,13 +107,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir handleConfigChangeOnHandlerThread(); } }, UserHandle.USER_ALL); - - // Add async callbacks for changes to server-side flags: some of the flags affect device / - // user config. All changes can be treated like a config change. If flags that affect config - // haven't changed then call will be a no-op. - mDeviceConfig.addListener( - handlerExecutor, - this::handleConfigChangeOnHandlerThread); } private void handleConfigChangeOnHandlerThread() { @@ -140,10 +128,12 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) - .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setAutoDetectionSupported(isAutoDetectionSupported()) - .setGeoDetectionSupported(isGeoDetectionSupported()) + .setAutoDetectionFeatureSupported( + mServiceConfigAccessor.isAutoDetectionFeatureSupported()) + .setGeoDetectionFeatureSupported( + mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) + .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabled(isLocationEnabled(userId)) .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) .build(); @@ -186,18 +176,19 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir // time zone detection: if we wrote it down then we'd set the value explicitly, which would // prevent detecting "default" later. That might influence what happens on later releases // that support new types of auto detection on the same hardware. - if (isAutoDetectionSupported()) { + if (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); setAutoDetectionEnabledIfRequired(autoDetectionEnabled); - // Avoid writing the geo detection enabled setting for devices that do not support geo - // time zone detection: if we wrote it down then we'd set the value explicitly, which - // would prevent detecting "default" later. That might influence what happens on later - // releases that support geo detection on the same hardware. - // Also avoid writing the geo detection enabled setting for devices that are currently - // force-enabled: otherwise we might overwrite a droidfood user's real setting - // permanently. - if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) { + // Avoid writing the geo detection enabled setting for devices with settings that + // are currently overridden by server flags: otherwise we might overwrite a droidfood + // user's real setting permanently. + // Also avoid writing the geo detection enabled setting for devices that do not support + // geo time zone detection: if we wrote it down then we'd set the value explicitly, + // which would prevent detecting "default" later. That might influence what happens on + // later releases that start to support geo detection on the same hardware. + if (!mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride().isPresent() + && mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); } @@ -209,14 +200,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } - private boolean isAutoDetectionSupported() { - return deviceHasTelephonyNetwork() || isGeoDetectionSupported(); - } - - private boolean isGeoDetectionSupported() { - return mGeoDetectionSupported; - } - private boolean isAutoDetectionEnabled() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } @@ -237,24 +220,20 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private boolean isGeoDetectionEnabled(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection - // on for testers (where their other settings allow). - boolean forceEnabled = isGeoDetectionForceEnabled(); - if (forceEnabled) { - return true; + // on/off for testers (but only where their other settings would allow them to turn it on + // for themselves). + Optional<Boolean> override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); + if (override.isPresent()) { + return override.get(); } - final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false); + final boolean geoDetectionEnabledByDefault = + mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); return Settings.Secure.getIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } - private boolean isGeoDetectionForceEnabled() { - return mDeviceConfig.getBoolean( - DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false); - } - private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 if (isGeoDetectionEnabled(userId) != enabled) { @@ -262,10 +241,4 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir enabled ? 1 : 0, userId); } } - - private boolean deviceHasTelephonyNetwork() { - // TODO b/150583524 Avoid the use of a deprecated API. - return mContext.getSystemService(ConnectivityManager.class) - .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); - } } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java new file mode 100644 index 000000000000..86c32f8d7b45 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -0,0 +1,249 @@ +/* + * 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 com.android.server.timezonedetector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.os.SystemProperties; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.server.timedetector.ServerFlags; + +import java.time.Duration; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A singleton that provides access to service configuration for time zone detection. This hides how + * configuration is split between static, compile-time config and dynamic, server-pushed flags. It + * provides a rudimentary mechanism to signal when values have changed. + */ +public final class ServiceConfigAccessor { + + private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet( + new ArraySet<>(new String[] { + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS + })); + + // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); + // TODO(b/179488561): Put this back to 1 minute when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = + Duration.ofSeconds(20); + private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + + private static final Object SLOCK = new Object(); + + /** The singleton instance. Initialized once in {@link #getInstance(Context)}. */ + @GuardedBy("SLOCK") + @Nullable + private static ServiceConfigAccessor sInstance; + + @NonNull private final Context mContext; + + /** + * An ultimate "feature switch" for location-based time zone detection. If this is + * {@code false}, the device cannot support the feature without a config change or a reboot: + * This affects what services are started on boot to minimize expense when the feature is not + * wanted. + */ + private final boolean mGeoDetectionFeatureSupportedInConfig; + + @NonNull private final ServerFlags mServerFlags; + + private ServiceConfigAccessor(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + + // The config value is expected to be the main feature flag. Platform developers can also + // force enable the feature using a persistent system property. Because system properties + // can change, this value is cached and only changes on reboot. + mGeoDetectionFeatureSupportedInConfig = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) + || SystemProperties.getBoolean( + "persist.sys.location_time_zone_detection_feature_supported", false); + + mServerFlags = ServerFlags.getInstance(mContext); + } + + /** Returns the singleton instance. */ + public static ServiceConfigAccessor getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServiceConfigAccessor(context); + } + return sInstance; + } + } + + /** + * Adds a listener that will be called server flags related to this class change. The callbacks + * are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove + * method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener) { + mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH); + } + + /** Returns {@code true} if any form of automatic time zone detection is supported. */ + public boolean isAutoDetectionFeatureSupported() { + return deviceHasTelephonyNetwork() || isGeoTimeZoneDetectionFeatureSupported(); + } + + private boolean deviceHasTelephonyNetwork() { + // TODO b/150583524 Avoid the use of a deprecated API. + return mContext.getSystemService(ConnectivityManager.class) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } + + /** + * Returns {@code true} if the location-based time zone detection feature can be supported on + * this device at all according to config. When {@code false}, implies that various other + * location-based settings will be turned off or rendered meaningless. Typically {@link + * #isGeoTimeZoneDetectionFeatureSupported()} should be used instead. + */ + public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() { + return mGeoDetectionFeatureSupportedInConfig; + } + + /** + * Returns {@code true} if the location-based time zone detection feature is supported on the + * device. This can be used during feature testing on builds that are capable of location time + * zone detection to enable / disable the feature for some users. + */ + public boolean isGeoTimeZoneDetectionFeatureSupported() { + return mGeoDetectionFeatureSupportedInConfig + && isGeoTimeZoneDetectionFeatureSupportedInternal(); + } + + private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() { + final boolean defaultEnabled = true; + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + defaultEnabled); + } + + /** + * Returns {@code true} if the primary location time zone provider can be used. + */ + public boolean isPrimaryLocationTimeZoneProviderEnabled() { + return getPrimaryLocationTimeZoneProviderEnabledOverride() + .orElse(isPrimaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isPrimaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getPrimaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns {@code true} if the secondary location time zone provider can be used. + */ + public boolean isSecondaryLocationTimeZoneProviderEnabled() { + return getSecondaryLocationTimeZoneProviderEnabledOverride() + .orElse(isSecondaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isSecondaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getSecondaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns whether location time zone detection is enabled for users when there's no setting + * value. Intended for use during feature release testing to "opt-in" users that haven't shown + * an explicit preference. + */ + public boolean isGeoDetectionEnabledForUsersByDefault() { + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, false); + } + + /** + * Returns whether location time zone detection is force enabled/disabled for users. Intended + * for use during feature release testing to force a given state. + */ + @NonNull + public Optional<Boolean> getGeoDetectionSettingEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); + } + + /** + * Returns the time to send to a location time zone provider that informs it how long it has + * to return its first time zone suggestion. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeout() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + } + + /** + * Returns the time added to {@link #getLocationTimeZoneProviderInitializationTimeout()} by the + * server before unilaterally declaring the provider is uncertain. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + } + + /** + * Returns the time after uncertainty is detected by providers before the location time zone + * manager makes a suggestion to the time zone detector. + */ + @NonNull + public Duration getLocationTimeZoneUncertaintyDelay() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + } + + private boolean getConfigBoolean(int providerEnabledConfigId) { + Resources resources = mContext.getResources(); + return resources.getBoolean(providerEnabledConfigId); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index 203a8a4e02cc..cd220b164851 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -27,16 +27,21 @@ import android.annotation.NonNull; */ public interface TimeZoneDetectorInternal extends Dumpable.Container { - /** Adds a listener that will be invoked when time zone detection configuration is changed. */ - void addConfigurationListener(ConfigurationChangeListener listener); + /** Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. */ + void addConfigurationListener(@NonNull ConfigurationChangeListener listener); /** * Removes a listener previously added via {@link * #addConfigurationListener(ConfigurationChangeListener)}. */ - void removeConfigurationListener(ConfigurationChangeListener listener); + void removeConfigurationListener(@NonNull ConfigurationChangeListener listener); - /** Returns the {@link ConfigurationInternal} for the current user. */ + /** + * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a + * snapshot so callers must use {@link #addConfigurationListener(ConfigurationChangeListener)} + * to be notified when it changes. + */ + @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index bd71ddf67094..c20400ae7a4b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -33,7 +33,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemProperties; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -62,27 +61,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub static final String TAG = "time_zone_detector"; /** - * A "feature switch" for location-based time zone detection. If this is {@code false}. It is - * initialized and never refreshed; it affects what services are started on boot so consistency - * is important. - */ - @Nullable - private static Boolean sGeoLocationTimeZoneDetectionSupported; - - /** Returns {@code true} if the location-based time zone detection feature is enabled. */ - public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) { - if (sGeoLocationTimeZoneDetectionSupported == null) { - // The config value is expected to be the main switch. Platform developers can also - // enable the feature using a persistent system property. - sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean( - com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) - || SystemProperties.getBoolean( - "persist.sys.location_time_zone_detection_feature_enabled", false); - } - return sGeoLocationTimeZoneDetectionSupported; - } - - /** * Handles the service lifecycle for {@link TimeZoneDetectorService} and * {@link TimeZoneDetectorInternalImpl}. */ @@ -98,11 +76,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Context context = getContext(); Handler handler = FgThread.getHandler(); - boolean geolocationTimeZoneDetectionSupported = - isGeoLocationTimeZoneDetectionSupported(context); + ServiceConfigAccessor serviceConfigAccessor = + ServiceConfigAccessor.getInstance(context); TimeZoneDetectorStrategy timeZoneDetectorStrategy = - TimeZoneDetectorStrategyImpl.create( - context, handler, geolocationTimeZoneDetectionSupported); + TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); // Create and publish the local service for use by internal callers. TimeZoneDetectorInternal internal = @@ -330,7 +307,8 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean isGeoTimeZoneDetectionSupported() { enforceManageTimeZoneDetectorPermission(); - return isGeoLocationTimeZoneDetectionSupported(mContext); + return ServiceConfigAccessor.getInstance(mContext) + .isGeoTimeZoneDetectionFeatureSupported(); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 0b1d6d71ea7b..8266f121822e 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -75,17 +75,21 @@ import android.util.IndentingPrintWriter; public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container { /** - * Sets a listener that will be triggered whenever time zone detection configuration is + * Adds a listener that will be triggered whenever {@link ConfigurationInternal} may have * changed. */ void addConfigChangeListener(@NonNull ConfigurationChangeListener listener); - /** Returns the user's time zone configuration. */ + /** + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * specified user. + */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** - * Returns the configuration that controls time zone detector behavior for the current user. + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * current user. */ @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index c464b74ca726..d163a0e22320 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -38,7 +38,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.timedetector.DeviceConfig; import java.util.ArrayList; import java.util.List; @@ -204,11 +203,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ public static TimeZoneDetectorStrategyImpl create( @NonNull Context context, @NonNull Handler handler, - boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { - DeviceConfig deviceConfig = new DeviceConfig(); - EnvironmentImpl environment = new EnvironmentImpl( - context, handler, deviceConfig, geoDetectionSupported); + Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor); return new TimeZoneDetectorStrategyImpl(environment); } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index e463ee22452d..98e984d2c3ac 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -19,9 +19,9 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import com.android.server.timezonedetector.ConfigurationChangeListener; import com.android.server.timezonedetector.ConfigurationInternal; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; import java.time.Duration; @@ -33,28 +33,19 @@ import java.util.Objects; */ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment { - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = - Duration.ofSeconds(20); - private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); - @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal; - @NonNull private final LocationTimeZoneProviderController mController; - @NonNull private final DeviceConfig mDeviceConfig; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final ConfigurationChangeListener mConfigurationChangeListener; ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull DeviceConfig deviceConfig, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull LocationTimeZoneProviderController controller) { super(threadingDomain); - mController = Objects.requireNonNull(controller); - mDeviceConfig = Objects.requireNonNull(deviceConfig); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class); // Listen for configuration changes. - mConfigurationChangeListener = () -> mThreadingDomain.post(mController::onConfigChanged); + mConfigurationChangeListener = () -> mThreadingDomain.post(controller::onConfigChanged); mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener); } @@ -73,24 +64,18 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir @Override @NonNull Duration getProviderInitializationTimeout() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeout(); } @Override @NonNull Duration getProviderInitializationTimeoutFuzz() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeoutFuzz(); } @Override @NonNull Duration getUncertaintyDelay() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay(); } } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index 364eaf8dac04..0d1692a8781d 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -21,11 +21,11 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -43,9 +43,8 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.SystemService; -import com.android.server.timedetector.DeviceConfig; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; -import com.android.server.timezonedetector.TimeZoneDetectorService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -96,28 +95,31 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneManagerService mService; + @NonNull + private final ServiceConfigAccessor mServerConfigAccessor; + public Lifecycle(@NonNull Context context) { super(Objects.requireNonNull(context)); + mServerConfigAccessor = ServiceConfigAccessor.getInstance(context); } @Override public void onStart() { Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { mService = new LocationTimeZoneManagerService(context); // The service currently exposes no LocalService or Binder API, but it extends // Binder and is registered as a binder service so it can receive shell commands. - publishBinderService("location_time_zone_manager", mService); + publishBinderService(SERVICE_NAME, mService); } else { - Slog.i(TAG, getClass() + " is disabled"); + Slog.d(TAG, "Geo time zone detection feature is disabled in config"); } } @Override - public void onBootPhase(int phase) { - Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + public void onBootPhase(@BootPhase int phase) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { if (phase == PHASE_SYSTEM_SERVICES_READY) { // The location service must be functioning after this boot phase. mService.onSystemReady(); @@ -159,6 +161,9 @@ public class LocationTimeZoneManagerService extends Binder { /** The shared lock from {@link #mThreadingDomain}. */ @NonNull private final Object mSharedLock; + @NonNull + private final ServiceConfigAccessor mServiceConfigAccessor; + // Lazily initialized. Can be null if the service has been stopped. @GuardedBy("mSharedLock") private ControllerImpl mLocationTimeZoneDetectorController; @@ -180,24 +185,38 @@ public class LocationTimeZoneManagerService extends Binder { mHandler = FgThread.getHandler(); mThreadingDomain = new HandlerThreadingDomain(mHandler); mSharedLock = mThreadingDomain.getLockObject(); + mServiceConfigAccessor = ServiceConfigAccessor.getInstance(mContext); } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemReady() { - // Called on an arbitrary thread during initialization. - synchronized (mSharedLock) { - // TODO(b/152744911): LocationManagerService watches for packages disappearing. Need to - // do anything here? + mServiceConfigAccessor.addListener(this::handleServiceConfigurationChangedOnMainThread); + } - // TODO(b/152744911): LocationManagerService watches for foreground app changes. Need to - // do anything here? - // TODO(b/152744911): LocationManagerService watches screen state. Need to do anything - // here? + private void handleServiceConfigurationChangedOnMainThread() { + // This method is called on the main thread, but service logic takes place on the threading + // domain thread, so we post the work there. + + // The way all service-level configuration changes are handled is to just restart this + // service - this is simple and effective, and service configuration changes should be rare. + mThreadingDomain.post(this::restartIfRequiredOnDomainThread); + } + + private void restartIfRequiredOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + // Stop and start the service, waiting until completion. + stopOnDomainThread(); + startOnDomainThread(); } } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemThirdPartyAppsCanStart() { - // Called on an arbitrary thread during initialization. We do not want to wait for - // completion as it would delay boot. + // Do not wait for completion as it would delay boot. final boolean waitForCompletion = false; startInternal(waitForCompletion); } @@ -205,6 +224,9 @@ public class LocationTimeZoneManagerService extends Binder { /** * Starts the service during server initialization or during tests after a call to * {@link #stop()}. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for + * completion, it cannot be called from the {@code mThreadingDomain} thread. */ void start() { enforceManageTimeZoneDetectorPermission(); @@ -214,28 +236,17 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Starts the service during server initialization or during tests after a call to - * {@link #stop()}. + * Starts the service during server initialization, if the configuration changes or during tests + * after a call to {@link #stop()}. * * <p>To avoid tests needing to sleep, when {@code waitForCompletion} is {@code true}, this * method will not return until all the system server components have started. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread, it cannot be + * called from the {@code mThreadingDomain} thread when {@code waitForCompletion} is true. */ private void startInternal(boolean waitForCompletion) { - Runnable runnable = () -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController == null) { - LocationTimeZoneProvider primary = createPrimaryProvider(); - LocationTimeZoneProvider secondary = createSecondaryProvider(); - mLocationTimeZoneDetectorController = - new ControllerImpl(mThreadingDomain, primary, secondary); - DeviceConfig deviceConfig = new DeviceConfig(); - mEnvironment = new ControllerEnvironmentImpl( - mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController); - ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); - mLocationTimeZoneDetectorController.initialize(mEnvironment, callback); - } - } - }; + Runnable runnable = this::startOnDomainThread; if (waitForCompletion) { mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS); } else { @@ -243,11 +254,38 @@ public class LocationTimeZoneManagerService extends Binder { } } + private void startOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (!mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { + debugLog("Not starting " + SERVICE_NAME + ": it is disabled in service config"); + return; + } + + if (mLocationTimeZoneDetectorController == null) { + LocationTimeZoneProvider primary = createPrimaryProvider(); + LocationTimeZoneProvider secondary = createSecondaryProvider(); + + ControllerImpl controller = + new ControllerImpl(mThreadingDomain, primary, secondary); + ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( + mThreadingDomain, mServiceConfigAccessor, controller); + ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); + controller.initialize(environment, callback); + + mEnvironment = environment; + mLocationTimeZoneDetectorController = controller; + } + } + } + + @NonNull private LocationTimeZoneProvider createPrimaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(PRIMARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -262,11 +300,12 @@ public class LocationTimeZoneManagerService extends Binder { return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy); } + @NonNull private LocationTimeZoneProvider createSecondaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(SECONDARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -282,33 +321,27 @@ public class LocationTimeZoneManagerService extends Binder { } /** Used for bug triage and in tests to simulate provider events. */ - private boolean isProviderInSimulationMode(String providerName) { + private boolean isProviderInSimulationMode(@NonNull String providerName) { return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED); } - /** Used for bug triage, tests and experiments to remove a provider. */ - private boolean isProviderDisabled(String providerName) { - return !isProviderEnabledInConfig(providerName) - || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED); - } + /** Used for bug triage, and by tests and experiments to remove a provider. */ + private boolean isProviderEnabled(@NonNull String providerName) { + if (isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED)) { + return false; + } - private boolean isProviderEnabledInConfig(String providerName) { - int providerEnabledConfigId; switch (providerName) { case PRIMARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isPrimaryLocationTimeZoneProviderEnabled(); } case SECONDARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isSecondaryLocationTimeZoneProviderEnabled(); } default: { throw new IllegalArgumentException(providerName); } } - Resources resources = mContext.getResources(); - return resources.getBoolean(providerEnabledConfigId); } private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) { @@ -326,22 +359,29 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Stops the service for tests. To avoid tests needing to sleep, this method will not return - * until all the system server components have stopped. + * Stops the service for tests and other rare cases. To avoid tests needing to sleep, this + * method will not return until all the system server components have stopped. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits it cannot + * be called from the {@code mThreadingDomain} thread. */ void stop() { enforceManageTimeZoneDetectorPermission(); - mThreadingDomain.postAndWait(() -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController != null) { - mLocationTimeZoneDetectorController.destroy(); - mLocationTimeZoneDetectorController = null; - mEnvironment.destroy(); - mEnvironment = null; - } + mThreadingDomain.postAndWait(this::stopOnDomainThread, BLOCKING_OP_WAIT_DURATION_MILLIS); + } + + private void stopOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController != null) { + mLocationTimeZoneDetectorController.destroy(); + mLocationTimeZoneDetectorController = null; + mEnvironment.destroy(); + mEnvironment = null; } - }, BLOCKING_OP_WAIT_DURATION_MILLIS); + } } @Override diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index b53150c729bc..bdf4a70a6a2b 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -21,6 +21,7 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND; @@ -102,7 +103,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); - pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:"); + pw.printf("Location Time Zone Manager (%s) commands for tests:\n", SERVICE_NAME); pw.println(" help"); pw.println(" Print this help text."); pw.printf(" %s\n", SHELL_COMMAND_START); diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 38211efc1c63..531c62c5b4e9 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -25,7 +25,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneManag import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -41,6 +40,7 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import java.util.Objects; import java.util.function.Predicate; @@ -123,7 +123,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { return resolves; } - private void onBind(IBinder binder, ComponentName componentName) { + private void onBind(IBinder binder, BoundService boundService) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java index 14820319d9df..e8386bc22403 100644 --- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java @@ -63,8 +63,6 @@ final class TimeZoneProviderRequest { return mSendUpdates; } - // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to - // be passed to the TimeZoneProviderService and remove if it is not useful. /** * Returns the maximum time that the provider is allowed to initialize before it is expected to * send an event of any sort. Only valid when {@link #sendUpdates()} is {@code true}. Failure to diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 9ee072ee7ce5..06748a3aa2d1 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -969,7 +969,7 @@ public class VcnGatewayConnection extends StateMachine { mGatewayStatusCallback.onGatewayConnectionError( mConnectionConfig.getRequiredUnderlyingCapabilities(), VCN_ERROR_CODE_INTERNAL_ERROR, - "java.lang.RuntimeException", + RuntimeException.class.getName(), "Received " + exception.getClass().getSimpleName() + " with message: " @@ -991,11 +991,11 @@ public class VcnGatewayConnection extends StateMachine { } else if (exception instanceof IkeInternalException && exception.getCause() instanceof IOException) { errorCode = VCN_ERROR_CODE_NETWORK_ERROR; - exceptionClass = "java.io.IOException"; + exceptionClass = IOException.class.getName(); exceptionMessage = exception.getCause().getMessage(); } else { errorCode = VCN_ERROR_CODE_INTERNAL_ERROR; - exceptionClass = "java.lang.RuntimeException"; + exceptionClass = RuntimeException.class.getName(); exceptionMessage = "Received " + exception.getClass().getSimpleName() diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5d3b9c197267..f40f4a98964a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -40,8 +40,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; @@ -209,6 +212,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; @@ -557,7 +561,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * The precomputed display insets for resolving configuration. It will be non-null if - * {@link #shouldUseSizeCompatMode} returns {@code true}. + * {@link #shouldCreateCompatDisplayInsets} returns {@code true}. */ private CompatDisplayInsets mCompatDisplayInsets; @@ -648,6 +652,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private Rect mSizeCompatBounds; + // Whether this activity is in size compatibility mode because its bounds don't fit in parent + // naturally. + private boolean mInSizeCompatModeForBounds = false; + + // Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + // orientation then aspect ratio restrictions are also already respected. + // This happens when an activity has fixed orientation which doesn't match orientation of the + // parent because a display is ignoring orientation request or fixed to user rotation. + // See WindowManagerService#getIgnoreOrientationRequest and + // WindowManagerService#getFixedToUserRotation for more context. + private boolean mIsLetterboxedForFixedOrientationAndAspectRatio = false; + // activity is not displayed? // TODO: rename to mNoDisplay @VisibleForTesting @@ -1296,9 +1312,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Maybe this call should be moved inside // updateOverrideConfiguration() newTask.updateOverrideConfigurationFromLaunchBounds(); - // Make sure override configuration is up-to-date before using to create window - // controller. - updateSizeCompatMode(); // When an activity is started directly into a split-screen fullscreen root task, we // need to update the initial multi-window modes so that the callbacks are scheduled // correctly when the user leaves that mode. @@ -3624,7 +3637,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Reset the last saved PiP snap fraction on removal. - mDisplayContent.mPinnedStackControllerLocked.onActivityHidden(mActivityComponent); + mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent); mWmService.mEmbeddedWindowController.onActivityRemoved(this); mRemovingFromDisplay = false; } @@ -4976,7 +4989,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this); mAppStopped = true; // Reset the last saved PiP snap fraction on app stop. - mDisplayContent.mPinnedStackControllerLocked.onActivityHidden(mActivityComponent); + mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent); destroySurfaces(); // Remove any starting window that was added for this app if they are still around. removeStartingWindow(); @@ -6718,12 +6731,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (onDescendantOrientationChanged(this)) { - // The app is just becoming visible, and the parent Task has updated with the - // orientation request. Update the size compat mode. - updateSizeCompatMode(); - // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure - // that WM Shell is called when an activity becomes visible. Without this, WM Core - // will handle positioning instead of WM Shell when an app is reopened. + // WM Shell can show additional UI elements, e.g. a restart button for size compat mode + // so ensure that WM Shell is called when an activity becomes visible. task.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } } @@ -6788,7 +6797,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * density than its parent or its bounds don't fit in parent naturally. */ boolean inSizeCompatMode() { - if (mCompatDisplayInsets == null || !shouldUseSizeCompatMode() + if (mInSizeCompatModeForBounds) { + return true; + } + if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; @@ -6798,70 +6810,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The app bounds hasn't been computed yet. return false; } - final Configuration parentConfig = getParent().getConfiguration(); // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these // fields should be changed with density and bounds, so here only compares the most // significant field. - if (parentConfig.densityDpi != getConfiguration().densityDpi) { - return true; - } - - final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); - final int appWidth = appBounds.width(); - final int appHeight = appBounds.height(); - final int parentAppWidth = parentAppBounds.width(); - final int parentAppHeight = parentAppBounds.height(); - if (parentAppWidth == appWidth && parentAppHeight == appHeight) { - // Matched the parent bounds. - return false; - } - if (parentAppWidth > appWidth && parentAppHeight > appHeight) { - // Both sides are smaller than the parent. - return true; - } - if (parentAppWidth < appWidth || parentAppHeight < appHeight) { - // One side is larger than the parent. - return true; - } - - // The rest of the condition is that only one side is smaller than the parent, but it still - // needs to exclude the cases where the size is limited by the fixed aspect ratio. - if (info.maxAspectRatio > 0) { - final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) - / Math.min(appWidth, appHeight); - if (aspectRatio >= info.maxAspectRatio) { - // The current size has reached the max aspect ratio. - return false; - } - } - if (info.minAspectRatio > 0) { - // The activity should have at least the min aspect ratio, so this checks if the parent - // still has available space to provide larger aspect ratio. - final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight)) - / Math.min(parentAppWidth, parentAppHeight); - if (parentAspectRatio <= info.minAspectRatio) { - // The long side has reached the parent. - return false; - } - } - return true; + return parentConfig.densityDpi != getConfiguration().densityDpi; } /** * Indicates the activity will keep the bounds and screen configuration when it was first * launched, no matter how its parent changes. * + * <p>If {@true}, then {@link CompatDisplayInsets} will be created in {@link + * #resolveOverrideConfiguration} to "freeze" activity bounds and insets. + * * @return {@code true} if this activity is declared as non-resizable and fixed orientation or * aspect ratio. */ - boolean shouldUseSizeCompatMode() { + boolean shouldCreateCompatDisplayInsets() { if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) { return false; } if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) { final ActivityRecord root = task != null ? task.getRootActivity() : null; - if (root != null && root != this && !root.shouldUseSizeCompatMode()) { + if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) { // If the root activity doesn't use size compatibility mode, the activities above // are forced to be the same for consistent visual appearance. return false; @@ -6883,25 +6855,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. - private void updateSizeCompatMode() { - if (mCompatDisplayInsets != null || !shouldUseSizeCompatMode()) { + private void updateCompatDisplayInsets(@Nullable Rect fixedOrientationBounds) { + if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } - final Configuration parentConfig = getParent().getConfiguration(); - if (!hasProcess() && !isConfigurationCompatible(parentConfig)) { - // Don't compute when launching in fullscreen and the fixed orientation is not the - // current orientation. It is more accurately to compute the override bounds from - // the updated configuration after the fixed orientation is applied. - return; - } - - if (task == null || (!handlesOrientationChangeFromDescendant() - && task.getLastTaskBoundsComputeActivity() != this)) { - // Don't compute when Task hasn't computed its bounds for this app, because the Task can - // be letterboxed, and its bounds may not be accurate until then. - return; - } Configuration overrideConfig = getRequestedOverrideConfiguration(); final Configuration fullConfig = getConfiguration(); @@ -6924,17 +6882,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // The role of CompatDisplayInsets is like the override bounds. - mCompatDisplayInsets = new CompatDisplayInsets(mDisplayContent, this); + mCompatDisplayInsets = + new CompatDisplayInsets(mDisplayContent, this, fixedOrientationBounds); } @VisibleForTesting void clearSizeCompatMode() { + mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; - // Recompute from Task because letterbox can also happen on Task level. - task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration()); + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } @Override @@ -6969,23 +6928,41 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTmpConfig.updateFrom(resolvedConfig); newParentConfiguration = mTmpConfig; } + + final int windowingMode = getWindowingMode(); + // TODO(b/181207944): Consider removing the if condition and always run + // resolveFixedOrientationConfiguration() since this should be applied for all cases. + if (isSplitScreenWindowingMode(windowingMode) + || windowingMode == WINDOWING_MODE_MULTI_WINDOW + || windowingMode == WINDOWING_MODE_FULLSCREEN) { + resolveFixedOrientationConfiguration(newParentConfiguration); + } + final Rect fixedOrientationBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedConfig.windowConfiguration.getBounds()) : null; + if (mCompatDisplayInsets != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); - } else { - if (inMultiWindowMode()) { - // We ignore activities' requested orientation in multi-window modes. Task level may - // take them into consideration when calculating bounds. - resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; - // If the activity has requested override bounds, the configuration needs to be - // computed accordingly. - if (!matchParentBounds()) { - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); - } - } else { - resolveFullscreenConfiguration(newParentConfiguration); + } else if (inMultiWindowMode()) { + // We ignore activities' requested orientation in multi-window modes. They may be + // taken into consideration in resolveFixedOrientationConfiguration call above. + resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; + // If the activity has requested override bounds, the configuration needs to be + // computed accordingly. + if (!matchParentBounds()) { + task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) { + resolveFullscreenConfiguration(newParentConfiguration); } + if (mVisibleRequested) { + updateCompatDisplayInsets(fixedOrientationBounds); + } + + // TODO(b/175212232): Consolidate position logic from each "resolve" method above here. + // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. @@ -6994,6 +6971,109 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + * orientation then aspect ratio restrictions are also already respected. + * + * <p>This happens when an activity has fixed orientation which doesn't match orientation of the + * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link + * WindowManagerService#getIgnoreOrientationRequest} for more context. + */ + boolean isLetterboxedForFixedOrientationAndAspectRatio() { + return mIsLetterboxedForFixedOrientationAndAspectRatio; + } + + /** + * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation + * change and the requested orientation is different from the parent. + * + * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied + * in this methiod. + */ + private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { + mIsLetterboxedForFixedOrientationAndAspectRatio = false; + if (handlesOrientationChangeFromDescendant()) { + // No need to letterbox because of fixed orientation. Display will handle + // fixed-orientation requests. + return; + } + + final Rect resolvedBounds = + getResolvedOverrideConfiguration().windowConfiguration.getBounds(); + final int parentOrientation = newParentConfig.orientation; + + // If the activity requires a different orientation (either by override or activityInfo), + // make it fit the available bounds by scaling down its bounds. + final int forcedOrientation = getRequestedConfigurationOrientation(); + if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { + return; + } + + if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { + // App prefers to keep its original size. + // If the size compat is from previous fixed orientation letterboxing, we may want to + // have fixed orientation letterbox again, otherwise it will show the size compat + // restart button even if the restart bounds will be the same. + return; + } + + final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); + final int parentWidth = parentBounds.width(); + final int parentHeight = parentBounds.height(); + float aspect = Math.max(parentWidth, parentHeight) + / (float) Math.min(parentWidth, parentHeight); + + // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in + // order to use the extra available space. + final float maxAspectRatio = info.maxAspectRatio; + final float minAspectRatio = info.minAspectRatio; + if (aspect > maxAspectRatio && maxAspectRatio != 0) { + aspect = maxAspectRatio; + } else if (aspect < minAspectRatio) { + aspect = minAspectRatio; + } + + // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. + // TODO(b/175212232): Rename getTaskLetterboxAspectRatio and all related methods since fixed + // orientation letterbox is on the activity level now. + final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); + // Activity min/max aspect ratio restrictions will be respected by the activity-level + // letterboxing (size-compat mode). Therefore this override can control the maximum screen + // area that can be occupied by the app in the letterbox mode. + aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO + ? letterboxAspectRatioOverride : aspect; + + // Store the current bounds to be able to revert to size compat mode values below if needed. + Rect mTmpFullBounds = new Rect(resolvedBounds); + if (forcedOrientation == ORIENTATION_LANDSCAPE) { + final int height = (int) Math.rint(parentWidth / aspect); + final int top = parentBounds.centerY() - height / 2; + resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height); + } else { + final int width = (int) Math.rint(parentHeight / aspect); + final int left = parentBounds.centerX() - width / 2; + resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); + } + + if (mCompatDisplayInsets != null) { + mCompatDisplayInsets.getBoundsByRotation( + mTmpBounds, newParentConfig.windowConfiguration.getRotation()); + if (resolvedBounds.width() != mTmpBounds.width() + || resolvedBounds.height() != mTmpBounds.height()) { + // The app shouldn't be resized, we only do fixed orientation letterboxing if the + // compat bounds are also from the same fixed orientation letterbox. Otherwise, + // clear the fixed orientation bounds to show app in size compat mode. + resolvedBounds.set(mTmpFullBounds); + return; + } + } + + // Calculate app bounds using fixed orientation bounds because they will be needed later + // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. + task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + mIsLetterboxedForFixedOrientationAndAspectRatio = true; + } + + /** * Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by * aspect ratio, the position will be centered horizontally in parent's app bounds to balance * the visual appearance. The policy of aspect ratio has higher priority than the requested @@ -7037,6 +7117,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds (stored in resolved bounds) instead of parent bounds since the + // activity will be displayed within them even if it is in size compat mode. They should be + // saved here before resolved bounds are overridden below. + final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedBounds) + : newParentConfiguration.windowConfiguration.getBounds(); + final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()) + : newParentConfiguration.windowConfiguration.getAppBounds(); + final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; final int orientation = orientationRequested @@ -7095,7 +7187,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Below figure is an example that puts an activity which was launched in a larger container // into a smaller container. // The outermost rectangle is the real display bounds. - // "@" is the parent app bounds. + // "@" is the container app bounds (parent bounds or fixed orientation bouds) // "#" is the {@code resolvedBounds} that applies to application. // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. // ------------------------------ @@ -7111,19 +7203,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The application is still layouted in "#" since it was launched, and it will be visually // scaled and positioned to "*". + final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); + // Calculates the scale and offset to horizontal center the size compatibility bounds into // the region which is available to application. - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); - final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); - final int viewportW = parentAppBounds.width(); - final int viewportH = parentAppBounds.height(); + final int viewportW = containerAppBounds.width(); + final int viewportH = containerAppBounds.height(); // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); - final int screenTopInset = parentAppBounds.top - parentBounds.top; + final int screenTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top; if (mSizeCompatScale != 1f || topNotAligned) { if (mSizeCompatBounds == null) { @@ -7143,8 +7234,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int offsetX = getHorizontalCenterOffset( (int) viewportW, (int) (contentW * mSizeCompatScale)); // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX; - final int screenPosY = parentBounds.top; + final int screenPosX = (fillContainer + ? containerBounds.left : containerAppBounds.left) + offsetX; + final int screenPosY = containerBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { mSizeCompatBounds.offset(screenPosX, screenPosY); @@ -7154,6 +7246,52 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int dy = screenPosY - resolvedBounds.top; offsetBounds(resolvedConfig, dx, dy); } + + mInSizeCompatModeForBounds = + isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); + } + + private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { + final int appWidth = appBounds.width(); + final int appHeight = appBounds.height(); + final int containerAppWidth = containerBounds.width(); + final int containerAppHeight = containerBounds.height(); + + if (containerAppWidth == appWidth && containerAppHeight == appHeight) { + // Matched the container bounds. + return false; + } + if (containerAppWidth > appWidth && containerAppHeight > appHeight) { + // Both sides are smaller than the container. + return true; + } + if (containerAppWidth < appWidth || containerAppHeight < appHeight) { + // One side is larger than the container. + return true; + } + + // The rest of the condition is that only one side is smaller than the container, but it + // still needs to exclude the cases where the size is limited by the fixed aspect ratio. + if (info.maxAspectRatio > 0) { + final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) + / Math.min(appWidth, appHeight); + if (aspectRatio >= info.maxAspectRatio) { + // The current size has reached the max aspect ratio. + return false; + } + } + if (info.minAspectRatio > 0) { + // The activity should have at least the min aspect ratio, so this checks if the + // container still has available space to provide larger aspect ratio. + final float containerAspectRatio = + (0.5f + Math.max(containerAppWidth, containerAppHeight)) + / Math.min(containerAppWidth, containerAppHeight); + if (containerAspectRatio <= info.minAspectRatio) { + // The long side has reached the parent. + return false; + } + } + return true; } /** @return The horizontal offset of putting the content in the center of viewport. */ @@ -7305,7 +7443,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task rootTask = getRootTask(); final float minAspectRatio = info.minAspectRatio; - if (task == null || rootTask == null || (inMultiWindowMode() && !shouldUseSizeCompatMode()) + if (task == null || rootTask == null + || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()) || (maxAspectRatio == 0 && minAspectRatio == 0) || isInVrUiMode(getConfiguration())) { // We don't enforce aspect ratio if the activity task is in multiwindow unless it @@ -7443,8 +7582,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (displayChanged) { mLastReportedDisplayId = newDisplayId; } - // TODO(b/36505427): Is there a better place to do this? - updateSizeCompatMode(); // Short circuit: if the two full configurations are equal (the common case), then there is // nothing to do. We test the full configuration instead of the global and merged override @@ -7723,11 +7860,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the existing override configuration so it can be updated according to the latest // configuration. clearSizeCompatMode(); - if (mVisibleRequested) { - // Configuration will be ensured when becoming visible, so if it is already visible, - // then the manual update is needed. - updateSizeCompatMode(); - } if (!attachedToProcess()) { return; @@ -8134,8 +8266,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final int mHeight; /** Whether the {@link Task} windowingMode represents a floating window*/ final boolean mIsFloating; - /** Whether the {@link Task} is letterboxed when the unresizable activity is first shown. */ - final boolean mIsTaskLetterboxed; + /** + * Whether is letterboxed because of fixed orientation when the unresizable activity is + * first shown. + */ + final boolean mIsInFixedOrientationLetterbox; /** * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It * is used to compute the appBounds. @@ -8149,7 +8284,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect[] mStableInsets = new Rect[4]; /** Constructs the environment to simulate the bounds behavior of the given container. */ - CompatDisplayInsets(DisplayContent display, ActivityRecord container) { + CompatDisplayInsets(DisplayContent display, ActivityRecord container, + @Nullable Rect fixedOrientationBounds) { mIsFloating = container.getWindowConfiguration().tasksAreFloating(); if (mIsFloating) { final Rect containerBounds = container.getWindowConfiguration().getBounds(); @@ -8162,24 +8298,34 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mNonDecorInsets[rotation] = emptyRect; mStableInsets[rotation] = emptyRect; } - mIsTaskLetterboxed = false; + mIsInFixedOrientationLetterbox = false; return; } final Task task = container.getTask(); - mIsTaskLetterboxed = task != null && task.isTaskLetterboxed(); + + mIsInFixedOrientationLetterbox = fixedOrientationBounds != null; // Store the bounds of the Task for the non-resizable activity to use in size compat // mode so that the activity will not be resized regardless the windowing mode it is // currently in. - final WindowContainer filledContainer = task != null ? task : display; - final Point dimensions = getRotationZeroDimensions(filledContainer); + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds instead of task bounds since the activity will be displayed + // within these even if it is in size compat mode. + final Rect filledContainerBounds = mIsInFixedOrientationLetterbox + ? fixedOrientationBounds + : task != null ? task.getBounds() : display.getBounds(); + final int filledContainerRotation = task != null + ? task.getConfiguration().windowConfiguration.getRotation() + : display.getConfiguration().windowConfiguration.getRotation(); + final Point dimensions = getRotationZeroDimensions( + filledContainerBounds, filledContainerRotation); mWidth = dimensions.x; mHeight = dimensions.y; // Bounds of the filled container if it doesn't fill the display. final Rect unfilledContainerBounds = - filledContainer.getBounds().equals(display.getBounds()) ? null : new Rect(); + filledContainerBounds.equals(display.getBounds()) ? null : new Rect(); final DisplayPolicy policy = display.getDisplayPolicy(); for (int rotation = 0; rotation < 4; rotation++) { mNonDecorInsets[rotation] = new Rect(); @@ -8199,9 +8345,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The insets is based on the display, but the container may be smaller than the // display, so update the insets to exclude parts that are not intersected with the // container. - unfilledContainerBounds.set(filledContainer.getBounds()); + unfilledContainerBounds.set(filledContainerBounds); display.rotateBounds( - filledContainer.getConfiguration().windowConfiguration.getRotation(), + filledContainerRotation, rotation, unfilledContainerBounds); updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]); @@ -8214,9 +8360,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * the display is rotated, we can calculate the bounds by rotating the dimensions. * @see #getBoundsByRotation */ - private static Point getRotationZeroDimensions(WindowContainer container) { - final Rect bounds = container.getBounds(); - final int rotation = container.getConfiguration().windowConfiguration.getRotation(); + private static Point getRotationZeroDimensions(final Rect bounds, int rotation) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int width = bounds.width(); final int height = bounds.height(); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 60ca725b118e..6ce9048d79e2 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1708,7 +1708,7 @@ class ActivityStarter { // If the activity being launched is the same as the one currently at the top, then // we need to check if it should only be launched once. - final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); + final Task topRootTask = mPreferredTaskDisplayArea.getFocusedRootTask(); if (topRootTask != null) { startResult = deliverToCurrentTopIfNeeded(topRootTask, intentGrants); if (startResult != START_SUCCESS) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index b803fc37a421..af032c108538 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2095,10 +2095,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - /** @return the max ANR delay from all registered {@link AnrController} instances */ - public long getMaxAnrDelayMillis(ApplicationInfo info) { + /** + * @return the controller with the max ANR delay from all registered + * {@link AnrController} instances + */ + @Nullable + public AnrController getAnrController(ApplicationInfo info) { if (info == null || info.packageName == null) { - return 0; + return null; } final ArrayList<AnrController> controllers; @@ -2107,12 +2111,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final String packageName = info.packageName; + final int uid = info.uid; long maxDelayMs = 0; + AnrController controllerWithMaxDelay = null; + for (AnrController controller : controllers) { - maxDelayMs = Math.max(maxDelayMs, controller.getAnrDelayMillis(packageName, info.uid)); + long delayMs = controller.getAnrDelayMillis(packageName, uid); + if (delayMs > 0 && delayMs > maxDelayMs) { + controllerWithMaxDelay = controller; + maxDelayMs = delayMs; + } } - maxDelayMs = Math.max(maxDelayMs, 0); - return maxDelayMs; + + return controllerWithMaxDelay; } @Override diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index f505daa9b628..a7312b321e4d 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -67,39 +67,40 @@ import java.util.function.BiFunction; * .setImeContainer(imeContainer) * .setTaskDisplayAreas(rootTdaList); * - * // Build root hierarchy of front and rear DisplayAreaGroup. - * RootDisplayArea frontRoot = new RootDisplayArea(wmService, "FrontRoot", FEATURE_FRONT_ROOT); - * DisplayAreaPolicyBuilder.HierarchyBuilder frontGroupHierarchy = - * new DisplayAreaPolicyBuilder.HierarchyBuilder(frontRoot) + * // Build root hierarchy of first and second DisplayAreaGroup. + * RootDisplayArea firstRoot = new RootDisplayArea(wmService, "FirstRoot", FEATURE_FIRST_ROOT); + * DisplayAreaPolicyBuilder.HierarchyBuilder firstGroupHierarchy = + * new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) * // (Optional) .addFeature(...) - * .setTaskDisplayAreas(frontTdaList); + * .setTaskDisplayAreas(firstTdaList); * - * RootDisplayArea rearRoot = new RootDisplayArea(wmService, "RearRoot", FEATURE_REAR_ROOT); - * DisplayAreaPolicyBuilder.HierarchyBuilder rearGroupHierarchy = - * new DisplayAreaPolicyBuilder.HierarchyBuilder(rearRoot) + * RootDisplayArea secondRoot = new RootDisplayArea(wmService, "SecondRoot", + * FEATURE_REAR_ROOT); + * DisplayAreaPolicyBuilder.HierarchyBuilder secondGroupHierarchy = + * new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot) * // (Optional) .addFeature(...) - * .setTaskDisplayAreas(rearTdaList); + * .setTaskDisplayAreas(secondTdaList); * * // Define the function to select root for window to attach. * BiFunction<WindowToken, Bundle, RootDisplayArea> selectRootForWindowFunc = - * (windowToken, bundle) -> { - * if (bundle == null) { + * (windowToken, options) -> { + * if (options == null) { * return root; * } * // OEMs need to define the condition. * if (...) { - * return frontRoot; + * return firstRoot; * } * if (...) { - * return rearRoot; + * return secondRoot; * } * return root; * }; * * return new DisplayAreaPolicyBuilder() * .setRootHierarchy(rootHierarchy) - * .addDisplayAreaGroupHierarchy(frontGroupHierarchy) - * .addDisplayAreaGroupHierarchy(rearGroupHierarchy) + * .addDisplayAreaGroupHierarchy(firstGroupHierarchy) + * .addDisplayAreaGroupHierarchy(secondGroupHierarchy) * .setSelectRootForWindowFunc(selectRootForWindowFunc) * .build(wmService, content); * </pre> @@ -110,12 +111,12 @@ import java.util.function.BiFunction; * - WindowedMagnification * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea - * - RootDisplayArea (FrontRoot) + * - RootDisplayArea (FirstRoot) * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME can be attached here) * - DisplayArea.Tokens (windows above IME can be attached here) - * - RootDisplayArea (RearRoot) + * - RootDisplayArea (SecondRoot) * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME can be attached here) @@ -133,14 +134,23 @@ import java.util.function.BiFunction; * {@link RootDisplayArea}. */ class DisplayAreaPolicyBuilder { + + /** + * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the + * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)} + */ + static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id"; + @Nullable private HierarchyBuilder mRootHierarchyBuilder; - private ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders = new ArrayList<>(); + private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders = + new ArrayList<>(); /** * When a window is created, the policy will use this function, which takes window type and * options, to select the {@link RootDisplayArea} to place that window in. The selected root * can be either the one of the {@link #mRootHierarchyBuilder} or the one of any of the * {@link #mDisplayAreaGroupHierarchyBuilders}. + * @see DefaultSelectRootForWindowFunction as an example. **/ @Nullable private BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc; @@ -160,7 +170,10 @@ class DisplayAreaPolicyBuilder { return this; } - /** The policy will use this function to find the root to place windows in. */ + /** + * The policy will use this function to find the root to place windows in. + * @see DefaultSelectRootForWindowFunction as an example. + */ DisplayAreaPolicyBuilder setSelectRootForWindowFunc( BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) { mSelectRootForWindowFunc = selectRootForWindowFunc; @@ -169,7 +182,7 @@ class DisplayAreaPolicyBuilder { /** * Makes sure the setting meets the requirement: - * 1. {@link mRootHierarchyBuilder} must be set. + * 1. {@link #mRootHierarchyBuilder} must be set. * 2. {@link RootDisplayArea} and {@link TaskDisplayArea} must have unique ids. * 3. {@link Feature} below the same {@link RootDisplayArea} must have unique ids. * 4. There must be exactly one {@link HierarchyBuilder} that contains the IME container. @@ -309,11 +322,57 @@ class DisplayAreaPolicyBuilder { hierarchyBuilder.build(); displayAreaGroupRoots.add(hierarchyBuilder.mRoot); } + // Use the default function if it is not specified otherwise. + if (mSelectRootForWindowFunc == null) { + mSelectRootForWindowFunc = new DefaultSelectRootForWindowFunction( + mRootHierarchyBuilder.mRoot, displayAreaGroupRoots); + } return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots, mSelectRootForWindowFunc); } /** + * The default function to be used for finding {@link RootDisplayArea} for window to be attached + * to if there is no other function set through {@link #setSelectRootForWindowFunc(BiFunction)}. + * + * When a window is created with {@link Bundle} options, this function will select the + * {@link RootDisplayArea} based on the options. Returns {@link #mDisplayRoot} if there is no + * match found. + */ + private static class DefaultSelectRootForWindowFunction implements + BiFunction<Integer, Bundle, RootDisplayArea> { + final RootDisplayArea mDisplayRoot; + final List<RootDisplayArea> mDisplayAreaGroupRoots; + + DefaultSelectRootForWindowFunction(RootDisplayArea displayRoot, + List<RootDisplayArea> displayAreaGroupRoots) { + mDisplayRoot = displayRoot; + mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots); + } + + @Override + public RootDisplayArea apply(Integer windowType, Bundle options) { + if (mDisplayAreaGroupRoots.isEmpty()) { + return mDisplayRoot; + } + + // Select the RootDisplayArea set in options. + if (options != null && options.containsKey(KEY_ROOT_DISPLAY_AREA_ID)) { + final int rootId = options.getInt(KEY_ROOT_DISPLAY_AREA_ID); + if (mDisplayRoot.mFeatureId == rootId) { + return mDisplayRoot; + } + for (int i = mDisplayAreaGroupRoots.size() - 1; i >= 0; i--) { + if (mDisplayAreaGroupRoots.get(i).mFeatureId == rootId) { + return mDisplayAreaGroupRoots.get(i); + } + } + } + return mDisplayRoot; + } + } + + /** * Builder to define {@link Feature} and {@link DisplayArea} hierarchy under a * {@link RootDisplayArea} */ @@ -672,15 +731,10 @@ class DisplayAreaPolicyBuilder { Result(WindowManagerService wmService, RootDisplayArea root, List<RootDisplayArea> displayAreaGroupRoots, - @Nullable BiFunction<Integer, Bundle, RootDisplayArea> - selectRootForWindowFunc) { + BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) { super(wmService, root); mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots); - mSelectRootForWindowFunc = selectRootForWindowFunc == null - // Always return the highest level root of the logical display when the func is - // not specified. - ? (type, options) -> mRoot - : selectRootForWindowFunc; + mSelectRootForWindowFunc = selectRootForWindowFunc; // Cache the default TaskDisplayArea for quick access. mDefaultTaskDisplayArea = mRoot.getItemFromTaskDisplayAreas(taskDisplayArea -> diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0143e70f53ca..eff4ea6536bd 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -36,6 +36,7 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Build.VERSION_CODES.N; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.DisplayMetrics.DENSITY_DEFAULT; +import static android.util.RotationUtils.deltaRotation; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; import static android.view.Display.FLAG_PRIVATE; @@ -47,7 +48,6 @@ import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; @@ -150,7 +150,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; -import android.app.WindowConfiguration; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -158,10 +157,8 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Insets; -import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.graphics.Region.Op; import android.hardware.HardwareBuffer; @@ -206,6 +203,7 @@ import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; import android.view.RoundedCorners; import android.view.Surface; +import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; @@ -449,8 +447,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Save allocating when calculating rects */ private final Rect mTmpRect = new Rect(); private final Rect mTmpRect2 = new Rect(); - private final RectF mTmpRectF = new RectF(); - private final Matrix mTmpMatrix = new Matrix(); private final Region mTmpRegion = new Region(); /** Used for handing back size of display */ @@ -461,8 +457,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Remove this display when animation on it has completed. */ private boolean mDeferredRemoval; - final DockedStackDividerController mDividerControllerLocked; - final PinnedStackController mPinnedStackControllerLocked; + final DockedTaskDividerController mDividerControllerLocked; + final PinnedTaskController mPinnedTaskControllerLocked; final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>(); /** A collection of windows that provide tap exclude regions inside of them. */ @@ -1012,8 +1008,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayPolicy.systemReady(); } mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius(); - mDividerControllerLocked = new DockedStackDividerController(this); - mPinnedStackControllerLocked = new PinnedStackController(mWmService, this); + mDividerControllerLocked = new DockedTaskDividerController(this); + mPinnedTaskControllerLocked = new PinnedTaskController(mWmService, this); final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession) .setOpaque(true) @@ -1289,7 +1285,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mInsetsPolicy; } - @Surface.Rotation + @Rotation int getRotation() { return mDisplayRotation.getRotation(); } @@ -1479,7 +1475,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Returns a valid rotation if the activity can use different orientation than the display. * Otherwise {@link #ROTATION_UNDEFINED}. */ - @Surface.Rotation + @Rotation int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) { if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { return ROTATION_UNDEFINED; @@ -1567,7 +1563,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // has it own policy for bounds, the activity bounds based on parent is unknown. return false; } - if (mPinnedStackControllerLocked.isPipActiveOrWindowingModeChanging()) { + if (mPinnedTaskControllerLocked.isPipActiveOrWindowingModeChanging()) { // Use normal rotation animation because seamless PiP rotation is not supported yet. return false; } @@ -1614,7 +1610,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Sets the provided record to {@link #mFixedRotationLaunchingApp} if possible to apply fixed * rotation transform to it and indicate that the display may be rotated after it is launched. */ - void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Surface.Rotation int rotation) { + void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) { final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp; if (prevRotatedLaunchingApp == r && r.getWindowConfiguration().getRotation() == rotation) { @@ -2296,12 +2292,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - DockedStackDividerController getDockedDividerController() { + DockedTaskDividerController getDockedDividerController() { return mDividerControllerLocked; } - PinnedStackController getPinnedStackController() { - return mPinnedStackControllerLocked; + PinnedTaskController getPinnedTaskController() { + return mPinnedTaskControllerLocked; } /** @@ -2382,10 +2378,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * for bounds calculations. */ void preOnConfigurationChanged() { - final PinnedStackController pinnedStackController = getPinnedStackController(); + final PinnedTaskController pinnedTaskController = getPinnedTaskController(); - if (pinnedStackController != null) { - getPinnedStackController().onConfigurationChanged(); + if (pinnedTaskController != null) { + getPinnedTaskController().onConfigurationChanged(); } } @@ -2929,7 +2925,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final boolean imeVisible = imeWin != null && imeWin.isVisible() && imeWin.isDisplayed(); final int imeHeight = getInputMethodWindowVisibleHeight(); - mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight); + mPinnedTaskControllerLocked.setAdjustedForIme(imeVisible, imeHeight); } int getInputMethodWindowVisibleHeight() { @@ -2951,27 +2947,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp forAllRootTasks(Task::prepareFreezingTaskBounds); } - void rotateBounds(int oldRotation, int newRotation, Rect bounds) { - getBounds(mTmpRect, newRotation); - rotateBounds(mTmpRect, oldRotation, newRotation, bounds); - } - - void rotateBounds(Rect parentBounds, int oldRotation, int newRotation, Rect bounds) { - // Compute a transform matrix to undo the coordinate space transformation, - // and present the window at the same physical position it previously occupied. - final int deltaRotation = deltaRotation(newRotation, oldRotation); - createRotationMatrix( - deltaRotation, parentBounds.width(), parentBounds.height(), mTmpMatrix); - - mTmpRectF.set(bounds); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.round(bounds); - } - - static int deltaRotation(int oldRotation, int newRotation) { - int delta = newRotation - oldRotation; - if (delta < 0) delta += 4; - return delta; + void rotateBounds(@Rotation int oldRotation, @Rotation int newRotation, Rect inOutBounds) { + // Get display bounds on oldRotation as parent bounds for the rotation. + getBounds(mTmpRect, oldRotation); + RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation); } public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) { @@ -2985,35 +2964,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mScreenRotationAnimation; } - private static void createRotationMatrix(int rotation, float displayWidth, float displayHeight, - Matrix outMatrix) { - // For rotations without Z-ordering we don't need the target rectangle's position. - createRotationMatrix(rotation, 0 /* rectLeft */, 0 /* rectTop */, displayWidth, - displayHeight, outMatrix); - } - - static void createRotationMatrix(int rotation, float rectLeft, float rectTop, - float displayWidth, float displayHeight, Matrix outMatrix) { - switch (rotation) { - case ROTATION_0: - outMatrix.reset(); - break; - case ROTATION_270: - outMatrix.setRotate(270, 0, 0); - outMatrix.postTranslate(0, displayHeight); - outMatrix.postTranslate(rectTop, 0); - break; - case ROTATION_180: - outMatrix.reset(); - break; - case ROTATION_90: - outMatrix.setRotate(90, 0, 0); - outMatrix.postTranslate(displayWidth, 0); - outMatrix.postTranslate(-rectTop, rectLeft); - break; - } - } - @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { @@ -3200,7 +3150,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } pw.println(); - mPinnedStackControllerLocked.dump(prefix, pw); + mPinnedTaskControllerLocked.dump(prefix, pw); pw.println(); mDisplayFrames.dump(prefix, pw); @@ -3639,8 +3589,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private boolean isImeControlledByApp() { - return mImeInputTarget != null && !WindowConfiguration.isSplitScreenWindowingMode( - mImeInputTarget.getWindowingMode()); + return mImeInputTarget != null && !mImeInputTarget.inMultiWindowMode(); } boolean isImeAttachedToApp() { @@ -4289,17 +4238,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp out.set(left, top, left + width, top + height); } - private void getBounds(Rect out, int orientation) { + private void getBounds(Rect out, @Rotation int rotation) { getBounds(out); // Rotate the Rect if needed. final int currentRotation = mDisplayInfo.rotation; - final int rotationDelta = deltaRotation(currentRotation, orientation); + final int rotationDelta = deltaRotation(currentRotation, rotation); if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { - createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix); - mTmpRectF.set(out); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.round(out); + out.set(0, 0, out.height(), out.width()); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 5460e36a5f1b..f64f04cc0829 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -24,6 +24,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; +import static android.util.RotationUtils.deltaRotation; +import static android.util.RotationUtils.rotateBounds; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -1046,11 +1048,17 @@ public class DisplayPolicy { return; } - // Get displayFrames bounds - sTmpDisplayFrameBounds.set(0, 0, displayFrames.mDisplayWidth, displayFrames.mDisplayHeight); + // Get displayFrames bounds as it is on WindowState's rotation. + final int deltaRotation = deltaRotation(windowRotation, displayFrames.mRotation); + if (deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270) { + sTmpDisplayFrameBounds.set( + 0, 0, displayFrames.mDisplayHeight, displayFrames.mDisplayWidth); + } else { + sTmpDisplayFrameBounds.set( + 0, 0, displayFrames.mDisplayWidth, displayFrames.mDisplayHeight); + } // Rotate the WindowState's bounds based on the displayFrames rotation - mDisplayContent.rotateBounds(sTmpDisplayFrameBounds, windowRotation, - displayFrames.mRotation, outBounds); + rotateBounds(outBounds, sTmpDisplayFrameBounds, deltaRotation); } /** diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b106657dee99..63cb38a59349 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.util.RotationUtils.deltaRotation; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; @@ -475,7 +476,7 @@ public class DisplayRotation { "Display id=%d rotation changed to %d from %d, lastOrientation=%d", displayId, rotation, oldRotation, lastOrientation); - if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) { + if (deltaRotation(oldRotation, rotation) != Surface.ROTATION_180) { mDisplayContent.mWaitingForConfig = true; } @@ -788,8 +789,13 @@ public class DisplayRotation { mFixedToUserRotation = fixedToUserRotation; mDisplayWindowSettings.setFixedToUserRotation(mDisplayContent, fixedToUserRotation); - mService.updateRotation(true /* alwaysSendConfiguration */, - false /* forceRelayout */); + if (mDisplayContent.mFocusedApp != null) { + // We record the last focused TDA that respects orientation request, check if this + // change may affect it. + mDisplayContent.onLastFocusedTaskDisplayAreaChanged( + mDisplayContent.mFocusedApp.getDisplayArea()); + } + mDisplayContent.updateOrientation(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java index de4bdaa57efa..fb9d06441536 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java @@ -19,16 +19,16 @@ package com.android.server.wm; import android.graphics.Rect; /** - * Keeps information about the docked stack divider. + * Keeps information about the docked task divider. */ -public class DockedStackDividerController { +public class DockedTaskDividerController { private final DisplayContent mDisplayContent; private boolean mResizing; private final Rect mTouchRegion = new Rect(); - DockedStackDividerController(DisplayContent displayContent) { + DockedTaskDividerController(DisplayContent displayContent) { mDisplayContent = displayContent; } @@ -58,6 +58,6 @@ public class DockedStackDividerController { private void resetDragResizingChangeReported() { mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported, - true /* traverseTopToBottom */ ); + true /* traverseTopToBottom */); } } diff --git a/services/core/java/com/android/server/wm/FactoryErrorDialog.java b/services/core/java/com/android/server/wm/FactoryErrorDialog.java index 88b5475cc3a2..afdf1ee4de7f 100644 --- a/services/core/java/com/android/server/wm/FactoryErrorDialog.java +++ b/services/core/java/com/android/server/wm/FactoryErrorDialog.java @@ -41,6 +41,11 @@ final class FactoryErrorDialog extends BaseErrorDialog { public void onStop() { } + @Override + protected void closeDialog() { + /* Do nothing */ + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { throw new RuntimeException("Rebooting from failed factory test"); diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 8fe2481f9eda..15e078b478b8 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -29,18 +29,18 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; import android.view.DisplayInfo; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** - * Holds the common state of the pinned stack between the system and SystemUI. If SystemUI ever + * Holds the common state of the pinned task between the system and SystemUI. If SystemUI ever * needs to be restarted, it will be notified with the last known state. * - * Changes to the pinned stack also flow through this controller, and generally, the system only - * changes the pinned stack bounds through this controller in two ways: + * Changes to the pinned task also flow through this controller, and generally, the system only + * changes the pinned task bounds through this controller in two ways: * * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio * and IME state into account. @@ -49,18 +49,18 @@ import java.util.List; * SystemUI adjustments (ie. expanded for menu, interaction, etc). * * Other changes in the system, including adjustment of IME, configuration change, and more are - * handled by SystemUI (similar to the docked stack divider). + * handled by SystemUI (similar to the docked task divider). */ -class PinnedStackController { +class PinnedTaskController { - private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM; + private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedTaskController" : TAG_WM; private final WindowManagerService mService; private final DisplayContent mDisplayContent; - private IPinnedStackListener mPinnedStackListener; - private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler = - new PinnedStackListenerDeathHandler(); + private IPinnedTaskListener mPinnedTaskListener; + private final PinnedTaskListenerDeathHandler mPinnedTaskListenerDeathHandler = + new PinnedTaskListenerDeathHandler(); /** Whether the PiP is entering or leaving. */ private boolean mIsPipWindowingModeChanging; @@ -72,7 +72,7 @@ class PinnedStackController { private ArrayList<RemoteAction> mActions = new ArrayList<>(); private float mAspectRatio = -1f; - // Used to calculate stack bounds across rotations + // Used to calculate task bounds across rotations private final DisplayInfo mDisplayInfo = new DisplayInfo(); // The aspect ratio bounds of the PIP. @@ -85,19 +85,19 @@ class PinnedStackController { /** * Handler for the case where the listener dies. */ - private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient { + private class PinnedTaskListenerDeathHandler implements IBinder.DeathRecipient { @Override public void binderDied() { // Clean up the state if the listener dies - if (mPinnedStackListener != null) { - mPinnedStackListener.asBinder().unlinkToDeath(mPinnedStackListenerDeathHandler, 0); + if (mPinnedTaskListener != null) { + mPinnedTaskListener.asBinder().unlinkToDeath(mPinnedTaskListenerDeathHandler, 0); } - mPinnedStackListener = null; + mPinnedTaskListener = null; } } - PinnedStackController(WindowManagerService service, DisplayContent displayContent) { + PinnedTaskController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); @@ -121,17 +121,17 @@ class PinnedStackController { } /** - * Registers a pinned stack listener. + * Registers a pinned task listener. */ - void registerPinnedStackListener(IPinnedStackListener listener) { + void registerPinnedTaskListener(IPinnedTaskListener listener) { try { - listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0); - mPinnedStackListener = listener; + listener.asBinder().linkToDeath(mPinnedTaskListenerDeathHandler, 0); + mPinnedTaskListener = listener; notifyImeVisibilityChanged(mIsImeShowing, mImeHeight); notifyMovementBoundsChanged(false /* fromImeAdjustment */); notifyActionsChanged(mActions); } catch (RemoteException e) { - Log.e(TAG, "Failed to register pinned stack listener", e); + Log.e(TAG, "Failed to register pinned task listener", e); } } @@ -139,8 +139,8 @@ class PinnedStackController { * @return whether the given {@param aspectRatio} is valid. */ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) { - return Float.compare(mMinAspectRatio, aspectRatio) <= 0 && - Float.compare(aspectRatio, mMaxAspectRatio) <= 0; + return Float.compare(mMinAspectRatio, aspectRatio) <= 0 + && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; } /** Returns {@code true} if the PiP is on screen or is changing windowing mode. */ @@ -152,7 +152,7 @@ class PinnedStackController { return pinnedTask != null && pinnedTask.hasChild(); } - /** Sets whether a visible stack is changing from or to pinned mode. */ + /** Sets whether a visible task is changing from or to pinned mode. */ void setPipWindowingModeChanging(boolean isPipWindowingModeChanging) { mIsPipWindowingModeChanging = isPipWindowingModeChanging; } @@ -162,9 +162,9 @@ class PinnedStackController { * so that the default bounds will be returned for the next session. */ void onActivityHidden(ComponentName componentName) { - if (mPinnedStackListener == null) return; + if (mPinnedTaskListener == null) return; try { - mPinnedStackListener.onActivityHidden(componentName); + mPinnedTaskListener.onActivityHidden(componentName); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e); } @@ -223,9 +223,9 @@ class PinnedStackController { * Notifies listeners that the PIP needs to be adjusted for the IME. */ private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) { - if (mPinnedStackListener != null) { + if (mPinnedTaskListener != null) { try { - mPinnedStackListener.onImeVisibilityChanged(imeVisible, imeHeight); + mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering bounds changed event.", e); } @@ -233,9 +233,9 @@ class PinnedStackController { } private void notifyAspectRatioChanged(float aspectRatio) { - if (mPinnedStackListener == null) return; + if (mPinnedTaskListener == null) return; try { - mPinnedStackListener.onAspectRatioChanged(aspectRatio); + mPinnedTaskListener.onAspectRatioChanged(aspectRatio); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e); } @@ -245,9 +245,9 @@ class PinnedStackController { * Notifies listeners that the PIP actions have changed. */ private void notifyActionsChanged(List<RemoteAction> actions) { - if (mPinnedStackListener != null) { + if (mPinnedTaskListener != null) { try { - mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions)); + mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions)); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } @@ -259,11 +259,11 @@ class PinnedStackController { */ private void notifyMovementBoundsChanged(boolean fromImeAdjustment) { synchronized (mService.mGlobalLock) { - if (mPinnedStackListener == null) { + if (mPinnedTaskListener == null) { return; } try { - mPinnedStackListener.onMovementBoundsChanged(fromImeAdjustment); + mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } @@ -271,7 +271,7 @@ class PinnedStackController { } void dump(String prefix, PrintWriter pw) { - pw.println(prefix + "PinnedStackController"); + pw.println(prefix + "PinnedTaskController"); pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mImeHeight=" + mImeHeight); pw.println(prefix + " mAspectRatio=" + mAspectRatio); diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 533c82e599c9..95a4f69edd57 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.util.RotationUtils.deltaRotation; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; @@ -174,7 +175,7 @@ class ScreenRotationAnimation { // apply rotation animation because there should be a top app shown as rotated. So the // specified original rotation customizes the direction of animation to have better look // when restoring the rotated app to the same rotation as current display. - final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation); + final int delta = deltaRotation(originalRotation, realOriginalRotation); final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270; mOriginalWidth = flipped ? originalHeight : originalWidth; mOriginalHeight = flipped ? originalWidth : originalHeight; @@ -330,7 +331,7 @@ class ScreenRotationAnimation { // Compute the transformation matrix that must be applied // to the snapshot to make it stay in the same original position // with the current screen rotation. - int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); + int delta = deltaRotation(rotation, Surface.ROTATION_0); RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); @@ -352,8 +353,7 @@ class ScreenRotationAnimation { mStarted = true; // Figure out how the screen has moved from the original rotation. - int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); - + int delta = deltaRotation(mCurRotation, mOriginalRotation); final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d60b6e0ef81d..7085156daa81 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -35,7 +35,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -148,7 +147,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -597,10 +595,6 @@ class Task extends WindowContainer<WindowContainer> { @Nullable private ActivityRecord mResumedActivity = null; - /** Last activity that is used to compute the Task bounds. */ - @Nullable - private ActivityRecord mLastTaskBoundsComputeActivity; - private boolean mForceShowForAllUsers; /** When set, will force the task to report as invisible. */ @@ -1496,11 +1490,6 @@ class Task extends WindowContainer<WindowContainer> { } void cleanUpActivityReferences(ActivityRecord r) { - // mLastTaskBoundsComputeActivity is set at leaf Task - if (mLastTaskBoundsComputeActivity == r) { - mLastTaskBoundsComputeActivity = null; - } - // mPausingActivity is set at leaf task if (mPausingActivity != null && mPausingActivity == r) { mPausingActivity = null; @@ -2267,7 +2256,7 @@ class Task extends WindowContainer<WindowContainer> { } if (pipChanging) { - mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(true); + mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(true); // If the top activity is using fixed rotation, it should be changing from PiP to // fullscreen with display orientation change. Do not notify fullscreen task organizer // because the restoration of task surface and the transformation of activity surface @@ -2297,7 +2286,7 @@ class Task extends WindowContainer<WindowContainer> { } } finally { if (pipChanging) { - mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(false); + mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(false); } } @@ -2361,9 +2350,7 @@ class Task extends WindowContainer<WindowContainer> { final int newRotation = getWindowConfiguration().getRotation(); final boolean rotationChanged = prevRotation != newRotation; if (rotationChanged) { - mDisplayContent.rotateBounds( - newParentConfig.windowConfiguration.getBounds(), prevRotation, newRotation, - newBounds); + mDisplayContent.rotateBounds(prevRotation, newRotation, newBounds); setBounds(newBounds); } } @@ -2865,7 +2852,6 @@ class Task extends WindowContainer<WindowContainer> { private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) { - mLastTaskBoundsComputeActivity = getTopNonFinishingActivity(false /* includeOverlays */); int windowingMode = getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); @@ -2879,7 +2865,8 @@ class Task extends WindowContainer<WindowContainer> { getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - computeFullscreenBounds(outOverrideBounds, newParentConfig); + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if // the parent or display is smaller than the size, the content may be cropped. return; @@ -2890,21 +2877,6 @@ class Task extends WindowContainer<WindowContainer> { computeFreeformBounds(outOverrideBounds, newParentConfig); return; } - - if (isSplitScreenWindowingMode(windowingMode) - || windowingMode == WINDOWING_MODE_MULTI_WINDOW) { - // This is to compute whether the task should be letterboxed to handle non-resizable app - // in multi window. There is no split screen only logic. - computeLetterboxBounds(outOverrideBounds, newParentConfig); - } - } - - /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */ - @VisibleForTesting - void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) { - // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent". - outBounds.setEmpty(); - computeLetterboxBounds(outBounds, newParentConfig); } /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ @@ -2936,94 +2908,6 @@ class Task extends WindowContainer<WindowContainer> { } } - /** - * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation - * change and the requested orientation is different from the parent. - */ - private void computeLetterboxBounds(@NonNull Rect outBounds, - @NonNull Configuration newParentConfig) { - if (handlesOrientationChangeFromDescendant()) { - // No need to letterbox at task level. Display will handle fixed-orientation requests. - return; - } - - final int parentOrientation = newParentConfig.orientation; - // Use the top activity as the reference of orientation. Don't include overlays because - // it is usually not the actual content or just temporarily shown. - // E.g. ForcedResizableInfoActivity. - final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */); - - // If the task or the reference activity requires a different orientation (either by - // override or activityInfo), make it fit the available bounds by scaling down its bounds. - final int overrideOrientation = getRequestedOverrideConfiguration().orientation; - final int forcedOrientation = - (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null) - ? overrideOrientation : refActivity.getRequestedConfigurationOrientation(); - if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { - return; - } - - final ActivityRecord.CompatDisplayInsets compatDisplayInsets = - refActivity == null ? null : refActivity.getCompatDisplayInsets(); - if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) { - // App prefers to keep its original size. - // If the size compat is from previous task letterboxing, we may want to have task - // letterbox again, otherwise it will show the size compat restart button even if the - // restart bounds will be the same. - return; - } - - final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); - final int parentWidth = parentBounds.width(); - final int parentHeight = parentBounds.height(); - float aspect = Math.max(parentWidth, parentHeight) - / (float) Math.min(parentWidth, parentHeight); - - // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the - // extra available space. - if (refActivity != null) { - final float maxAspectRatio = refActivity.info.maxAspectRatio; - final float minAspectRatio = refActivity.info.minAspectRatio; - if (aspect > maxAspectRatio && maxAspectRatio != 0) { - aspect = maxAspectRatio; - } else if (aspect < minAspectRatio) { - aspect = minAspectRatio; - } - } - - // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. - final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); - // Activity min/max aspect ratio restrictions will be respected by the activity-level - // letterboxing (size-compat mode). Therefore this override can control the maximum screen - // area that can be occupied by the app in the letterbox mode. - aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO - ? letterboxAspectRatioOverride : aspect; - - // Store the current bounds to be able to revert to size compat mode values below if needed. - mTmpFullBounds.set(outBounds); - if (forcedOrientation == ORIENTATION_LANDSCAPE) { - final int height = (int) Math.rint(parentWidth / aspect); - final int top = parentBounds.centerY() - height / 2; - outBounds.set(parentBounds.left, top, parentBounds.right, top + height); - } else { - final int width = (int) Math.rint(parentHeight / aspect); - final int left = parentBounds.centerX() - width / 2; - outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); - } - - if (compatDisplayInsets != null) { - compatDisplayInsets.getBoundsByRotation( - mTmpBounds, newParentConfig.windowConfiguration.getRotation()); - if (outBounds.width() != mTmpBounds.width() - || outBounds.height() != mTmpBounds.height()) { - // The app shouldn't be resized, we only do task letterboxing if the compat bounds - // is also from the same task letterbox. Otherwise, clear the task bounds to show - // app in size compat mode. - outBounds.set(mTmpFullBounds); - } - } - } - Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; @@ -3038,11 +2922,6 @@ class Task extends WindowContainer<WindowContainer> { return bounds; } - @Nullable - ActivityRecord getLastTaskBoundsComputeActivity() { - return mLastTaskBoundsComputeActivity; - } - /** Updates the task's bounds and override configuration to match what is expected for the * input root task. */ void updateOverrideConfigurationForRootTask(Task inRootTask) { @@ -3941,12 +3820,6 @@ class Task extends WindowContainer<WindowContainer> { || activityType == ACTIVITY_TYPE_ASSISTANT; } - boolean isTaskLetterboxed() { - // No letterbox for multi window root task - return !matchParentBounds() - && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask()); - } - @Override boolean fillsParent() { // From the perspective of policy, we still want to report that this task fills parent @@ -7792,18 +7665,18 @@ class Task extends WindowContainer<WindowContainer> { return; } - final PinnedStackController pinnedStackController = - getDisplayContent().getPinnedStackController(); + final PinnedTaskController pinnedTaskController = + getDisplayContent().getPinnedTaskController(); - if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) { + if (Float.compare(aspectRatio, pinnedTaskController.getAspectRatio()) == 0) { return; } // Notify the pinned stack controller about aspect ratio change. // This would result a callback delivered from SystemUI to WM to start animation, // if the bounds are ought to be altered due to aspect ratio change. - pinnedStackController.setAspectRatio( - pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio) + pinnedTaskController.setAspectRatio( + pinnedTaskController.isValidPictureInPictureAspectRatio(aspectRatio) ? aspectRatio : -1f); } @@ -7819,7 +7692,7 @@ class Task extends WindowContainer<WindowContainer> { return; } - getDisplayContent().getPinnedStackController().setActions(actions); + getDisplayContent().getPinnedTaskController().setActions(actions); } /** Returns true if a removal action is still being deferred. */ diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 9b7257057836..04ec4bd83511 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -130,8 +130,8 @@ class TaskChangeNotificationController { l.onActivityForcedResizable((String) m.obj, m.arg1, m.arg2); }; - private final TaskStackConsumer mNotifyActivityDismissingDockedStack = (l, m) -> { - l.onActivityDismissingDockedStack(); + private final TaskStackConsumer mNotifyActivityDismissingDockedTask = (l, m) -> { + l.onActivityDismissingDockedTask(); }; private final TaskStackConsumer mNotifyActivityLaunchOnSecondaryDisplayFailed = (l, m) -> { @@ -235,7 +235,7 @@ class TaskChangeNotificationController { forAllRemoteListeners(mNotifyActivityForcedResizable, msg); break; case NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG: - forAllRemoteListeners(mNotifyActivityDismissingDockedStack, msg); + forAllRemoteListeners(mNotifyActivityDismissingDockedTask, msg); break; case NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG: forAllRemoteListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg); @@ -391,7 +391,7 @@ class TaskChangeNotificationController { void notifyActivityDismissingDockedRootTask() { mHandler.removeMessages(NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG); final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG); - forAllLocalListeners(mNotifyActivityDismissingDockedStack, msg); + forAllLocalListeners(mNotifyActivityDismissingDockedTask, msg); msg.sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index c0bce6be8c54..1531e56bf490 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -596,9 +596,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { try { synchronized (mGlobalLock) { final WindowContainer wc = WindowContainer.fromBinder(token.asBinder()); - if (wc == null) { - throw new IllegalArgumentException("Can't resolve window from token"); - } + if (wc == null) return false; final Task task = wc.asTask(); if (task == null) return false; if (!task.mCreatedByOrganizer) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 89d30408fe25..c0ccd81c9b15 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -230,7 +230,7 @@ import android.view.IDisplayWindowListener; import android.view.IDisplayWindowRotationController; import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.IRecentsAnimationRunner; import android.view.IRotationWatcher; import android.view.IScrollCaptureCallbacks; @@ -253,6 +253,7 @@ import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.RemoteAnimationAdapter; +import android.view.ScrollCaptureResponse; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -2393,15 +2394,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - // We may be deferring layout passes at the moment, but since the client is interested - // in the new out values right now we need to force a layout. - mWindowPlacerLocked.performSurfacePlacement(true /* force */); - + // Create surfaceControl before surface placement otherwise layout will be skipped + // (because WS.isGoneForLayout() is true when there is no surface. if (shouldRelayout) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); - - result = win.relayoutVisibleWindow(result); - try { result = createSurfaceControl(outSurfaceControl, result, win, winAnimator); } catch (Exception e) { @@ -2413,6 +2408,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); return 0; } + } + + // We may be deferring layout passes at the moment, but since the client is interested + // in the new out values right now we need to force a layout. + mWindowPlacerLocked.performSurfacePlacement(true /* force */); + + if (shouldRelayout) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); + + result = win.relayoutVisibleWindow(result); + if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { focusMayChange = true; } @@ -2981,7 +2987,7 @@ public class WindowManagerService extends IWindowManager.Stub } boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) { - return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio( + return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio( aspectRatio); } @@ -6880,7 +6886,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setDockedStackDividerTouchRegion(Rect touchRegion) { + public void setDockedTaskDividerTouchRegion(Rect touchRegion) { synchronized (mGlobalLock) { final DisplayContent dc = getDefaultDisplayContentLocked(); dc.getDockedDividerController().setTouchRegion(touchRegion); @@ -6905,10 +6911,9 @@ public class WindowManagerService extends IWindowManager.Stub return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); } - @Override - public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) { + public void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener) { if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS, - "registerPinnedStackListener()")) { + "registerPinnedTaskListener()")) { return; } if (!mAtmService.mSupportsPictureInPicture) { @@ -6916,7 +6921,7 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - displayContent.getPinnedStackController().registerPinnedStackListener(listener); + displayContent.getPinnedTaskController().registerPinnedTaskListener(listener); } } @@ -7155,12 +7160,14 @@ public class WindowManagerService extends IWindowManager.Stub } final long token = Binder.clearCallingIdentity(); try { + ScrollCaptureResponse.Builder responseBuilder = new ScrollCaptureResponse.Builder(); synchronized (mGlobalLock) { DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null) { ProtoLog.e(WM_ERROR, "Invalid displayId for requestScrollCapture: %d", displayId); - callbacks.onUnavailable(); + responseBuilder.setDescription(String.format("bad displayId: %d", displayId)); + callbacks.onScrollCaptureResponse(responseBuilder.build()); return; } WindowState topWindow = null; @@ -7169,17 +7176,20 @@ public class WindowManagerService extends IWindowManager.Stub } WindowState targetWindow = dc.findScrollCaptureTargetWindow(topWindow, taskId); if (targetWindow == null) { - callbacks.onUnavailable(); + responseBuilder.setDescription("findScrollCaptureTargetWindow returned null"); + callbacks.onScrollCaptureResponse(responseBuilder.build()); return; } - // Forward to the window for handling. try { + // Forward to the window for handling, which will respond using the callback. targetWindow.mClient.requestScrollCapture(callbacks); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "requestScrollCapture: caught exception dispatching to window." + "token=%s", targetWindow.mClient.asBinder()); - callbacks.onUnavailable(); + responseBuilder.setWindowTitle(targetWindow.getName()); + responseBuilder.setDescription(String.format("caught exception: %s", e)); + callbacks.onScrollCaptureResponse(responseBuilder.build()); } } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 63a083261614..9973664346f0 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.Manifest.permission.READ_FRAME_BUFFER; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; @@ -426,8 +425,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (windowingMode > -1) { - if (mService.isInLockTaskMode() && windowingMode != WINDOWING_MODE_FULLSCREEN) { - throw new UnsupportedOperationException("Not supported to set non-fullscreen" + if (mService.isInLockTaskMode() + && WindowConfiguration.inMultiWindowMode(windowingMode)) { + throw new UnsupportedOperationException("Not supported to set multi-window" + " windowing mode during locked task mode."); } container.setWindowingMode(windowingMode); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 60e95e5cfd6e..1fc7041c0fe2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1224,7 +1224,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // But in docked we want to behave like fullscreen and behave as if the task // were given smaller bounds for the purposes of layout. Skip adjustments for // the root pinned task, they are handled separately in the - // PinnedStackController. + // PinnedTaskController. windowFrames.mContainingFrame.bottom = windowFrames.mParentFrame.bottom; } } @@ -3827,13 +3827,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @return true when the window should be letterboxed. */ boolean isLetterboxedAppWindow() { // Fullscreen mode but doesn't fill display area. - return (!inMultiWindowMode() && !matchesDisplayAreaBounds()) - // Activity in size compat. - || (mActivityRecord != null && mActivityRecord.inSizeCompatMode()) - // Task letterboxed. - || (getTask() != null && getTask().isTaskLetterboxed()) - // Letterboxed for display cutout. - || isLetterboxedForDisplayCutout(); + if (!inMultiWindowMode() && !matchesDisplayAreaBounds()) { + return true; + } + if (mActivityRecord != null) { + // Activity in size compat. + if (mActivityRecord.inSizeCompatMode()) { + return true; + } + // Letterbox for fixed orientation. + if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) { + return true; + } + } + // Letterboxed for display cutout. + return isLetterboxedForDisplayCutout(); } /** Returns {@code true} if the window is letterboxed for the display cutout. */ diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index 4551d49d9e58..8c6d084fba99 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -86,7 +86,7 @@ static int compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) int pidfd = syscall(__NR_pidfd_open, pid, 0); err = -errno; - if (err < 0) { + if (pidfd < 0) { // Skip compaction if failed to open pidfd with any error return err; } @@ -232,21 +232,6 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job compactProcessOrFallback(pid, compactionFlags); } -static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal( - JNIEnv *env, jobject clazz, jboolean enable) { - bool success = true; - - if (enable) { - success = SetTaskProfiles(0, {"FreezerEnabled"}, true); - } else { - success = SetTaskProfiles(0, {"FreezerDisabled"}, true); - } - - if (!success) { - jniThrowException(env, "java/lang/RuntimeException", "Unknown error"); - } -} - static void com_android_server_am_CachedAppOptimizer_freezeBinder( JNIEnv *env, jobject clazz, jint pid, jboolean freeze) { @@ -280,15 +265,19 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env, jobject clazz) { - return env->NewStringUTF(CGROUP_FREEZE_PATH); + std::string path; + + if (!getAttributePathForTask("FreezerState", getpid(), &path)) { + path = ""; + } + + return env->NewStringUTF(path.c_str()); } static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, - {"enableFreezerInternal", "(Z)V", - (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal}, {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, {"getBinderFreezeInfo", "(I)I", (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 21d57d8c189f..10705af9ac38 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -331,7 +331,7 @@ public: uint32_t policyFlags) override; bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(bool enabled) override; @@ -1325,9 +1325,9 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token, return result; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) { ATRACE_CALL(); - android_server_PowerManagerService_userActivity(eventTime, eventType); + android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } bool NativeInputManager::checkInjectEventsPermissionNonReentrant( diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 63a6eedd9e66..9b7e27d891c4 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -103,7 +103,8 @@ static bool setPowerMode(Mode mode, bool enabled) { return result == power::HalResult::SUCCESSFUL; } -void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) { +void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, + int32_t displayId) { if (gPowerManagerServiceObj) { // Throttle calls into user activity by event type. // We're a little conservative about argument checking here in case the caller @@ -127,7 +128,7 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t env->CallVoidMethod(gPowerManagerServiceObj, gPowerManagerServiceClassInfo.userActivityFromNative, - nanoseconds_to_milliseconds(eventTime), eventType, 0); + nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0); checkAndClearExceptionFromCallback(env, "userActivityFromNative"); } } @@ -285,7 +286,7 @@ int register_android_server_PowerManagerService(JNIEnv* env) { FIND_CLASS(clazz, "com/android/server/power/PowerManagerService"); GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz, - "userActivityFromNative", "(JII)V"); + "userActivityFromNative", "(JIII)V"); // Initialize for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) { diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h index a17fd650522b..a2f335c74870 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.h +++ b/services/core/jni/com_android_server_power_PowerManagerService.h @@ -24,7 +24,8 @@ namespace android { -extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType); +extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, + int32_t displayId); } // namespace android diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index b52347f509f8..ef7afc8d1894 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -129,6 +129,15 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) { } + @Override + public void setDeviceOwnerType(@NonNull ComponentName admin, int deviceOwnerType) { + } + + @Override + public int getDeviceOwnerType(@NonNull ComponentName admin) { + return 0; + } + public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {} public boolean canAdminGrantSensorsPermissionsForUser(int userId) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7260732bbcb6..524892b2ec4b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -156,6 +156,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.DeviceOwnerType; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManager.PasswordComplexity; @@ -333,6 +334,7 @@ import com.google.android.collect.Sets; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -340,6 +342,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.text.DateFormat; import java.time.LocalDate; import java.util.ArrayList; @@ -3399,8 +3404,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(adminReceiver, "ComponentName is null"); - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceRemoveActiveAdmin"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS), + "Caller must be shell or hold MANAGE_PROFILE_AND_DEVICE_OWNERS to call " + + "forceRemoveActiveAdmin"); mInjector.binderWithCleanCallingIdentity(() -> { synchronized (getLockObject()) { if (!isAdminTestOnlyLocked(adminReceiver, userHandle)) { @@ -5622,7 +5629,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Get attestation flags, if any. final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags); final boolean deviceIdAttestationRequired = attestationUtilsFlags != null; - final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec(); + KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec(); final String alias = keySpec.getKeystoreAlias(); Preconditions.checkStringNotEmpty(alias, "Empty alias provided"); @@ -5643,6 +5650,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); } + if (TextUtils.isEmpty(alias)) { + throw new IllegalArgumentException("Empty alias provided."); + } // As the caller will be granted access to the key, ensure no UID was specified, as // it will not have the desired effect. if (keySpec.getUid() != KeyStore.UID_SELF) { @@ -5651,19 +5661,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } + if (deviceIdAttestationRequired) { + if (keySpec.getAttestationChallenge() == null) { + throw new IllegalArgumentException( + "Requested Device ID attestation but challenge is empty."); + } + KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(keySpec); + specBuilder.setAttestationIds(attestationUtilsFlags); + specBuilder.setDevicePropertiesAttestationIncluded(true); + keySpec = specBuilder.build(); + } + + final UserHandle userHandle = mInjector.binderGetCallingUserHandle(); final long id = mInjector.binderClearCallingIdentity(); try { try (KeyChainConnection keyChainConnection = - KeyChain.bindAsUser(mContext, caller.getUserHandle())) { + KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); - // Copy the provided keySpec, excluding the attestation challenge, which will be - // used later for requesting key attestation record. - final KeyGenParameterSpec noAttestationSpec = new KeyGenParameterSpec.Builder( - keySpec).setAttestationChallenge(null).build(); - final int generationResult = keyChain.generateKeyPair(algorithm, - new ParcelableKeyGenParameterSpec(noAttestationSpec)); + new ParcelableKeyGenParameterSpec(keySpec)); if (generationResult != KeyChain.KEY_GEN_SUCCESS) { Log.e(LOG_TAG, String.format( "KeyChain failed to generate a keypair, error %d.", generationResult)); @@ -5672,6 +5689,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new ServiceSpecificException( DevicePolicyManager.KEY_GEN_STRONGBOX_UNAVAILABLE, String.format("KeyChain error: %d", generationResult)); + case KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS: + throw new UnsupportedOperationException( + "Device does not support Device ID attestation."); default: logGenerateKeyPairFailure(caller, isCredentialManagementApp); return false; @@ -5685,23 +5705,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // that UID. keyChain.setGrant(caller.getUid(), alias, true); - final byte[] attestationChallenge = keySpec.getAttestationChallenge(); - if (attestationChallenge != null) { - final int attestationResult = keyChain.attestKey( - alias, attestationChallenge, attestationUtilsFlags, attestationChain); - if (attestationResult != KeyChain.KEY_ATTESTATION_SUCCESS) { - Log.e(LOG_TAG, String.format( - "Attestation for %s failed (rc=%d), deleting key.", - alias, attestationResult)); - keyChain.removeKeyPair(alias); - if (attestationResult == KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS) { - throw new UnsupportedOperationException( - "Device does not support Device ID attestation."); + try { + final List<byte[]> encodedCerts = new ArrayList(); + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final byte[] certChainBytes = keyChain.getCaCertificates(alias); + encodedCerts.add(keyChain.getCertificate(alias)); + if (certChainBytes != null) { + final Collection<X509Certificate> certs = + (Collection<X509Certificate>) certFactory.generateCertificates( + new ByteArrayInputStream(certChainBytes)); + for (X509Certificate cert : certs) { + encodedCerts.add(cert.getEncoded()); } - logGenerateKeyPairFailure(caller, isCredentialManagementApp); - return false; } + + attestationChain.shallowCopyFrom(new KeymasterCertificateChain(encodedCerts)); + } catch (CertificateException e) { + logGenerateKeyPairFailure(caller, isCredentialManagementApp); + Log.e(LOG_TAG, "While retrieving certificate chain.", e); + return false; } + DevicePolicyEventLogger .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR) .setAdmin(caller.getPackageName()) @@ -7560,8 +7584,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) { - sendActiveAdminCommand(action, extras, userId, - mOwners.getProfileOwnerComponent(userId)); + sendActiveAdminCommand(action, extras, userId, mOwners.getProfileOwnerComponent(userId)); } private void sendActiveAdminCommand(String action, Bundle extras, @@ -8089,7 +8112,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } if (!callingUserOnly) { - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); } synchronized (getLockObject()) { if (!mOwners.hasDeviceOwner()) { @@ -12351,8 +12375,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason); extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe); - sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, - extras); + if (mOwners.hasDeviceOwner()) { + sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras); + } for (int profileOwnerId : mOwners.getProfileOwnerKeys()) { sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, extras, profileOwnerId); @@ -12543,8 +12569,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void clearSystemUpdatePolicyFreezePeriodRecord() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call clearSystemUpdatePolicyFreezePeriodRecord"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.CLEAR_FREEZE_PERIOD), + "Caller must be shell, or hold CLEAR_FREEZE_PERIOD permission to call " + + "clearSystemUpdatePolicyFreezePeriodRecord"); synchronized (getLockObject()) { // Print out current record to help diagnosed CTS failures Slog.i(LOG_TAG, "Clear freeze period record: " @@ -13487,7 +13515,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); // Only adb or system apps with the right permission can mark a profile owner on // organization-owned device. - if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED))) { + if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED) + || hasCallingPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS))) { throw new SecurityException( "Only the system can mark a profile owner of organization-owned device."); } @@ -13806,8 +13835,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long forceSecurityLogs() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceSecurityLogs"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), + "Caller must be shell or hold FORCE_DEVICE_POLICY_MANAGER_LOGS to call " + + "forceSecurityLogs"); if (!mInjector.securityLogGetLoggingEnabledProperty()) { throw new IllegalStateException("logging is not available"); } @@ -14327,8 +14358,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long forceNetworkLogs() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceNetworkLogs"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), + "Caller must be shell or hold FORCE_DEVICE_POLICY_MANAGER_LOGS to call " + + "forceNetworkLogs"); synchronized (getLockObject()) { if (!isNetworkLoggingEnabledInternalLocked()) { throw new IllegalStateException("logging is not available"); @@ -16731,6 +16764,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setDeviceOwnerType(@NonNull ComponentName admin, + @DeviceOwnerType int deviceOwnerType) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + verifyDeviceOwnerTypePreconditions(admin); + + final String packageName = admin.getPackageName(); + Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName), + "The device owner type has already been set for " + packageName); + + synchronized (getLockObject()) { + mOwners.setDeviceOwnerType(packageName, deviceOwnerType); + } + } + + @Override + @DeviceOwnerType + public int getDeviceOwnerType(@NonNull ComponentName admin) { + verifyDeviceOwnerTypePreconditions(admin); + synchronized (getLockObject()) { + return mOwners.getDeviceOwnerType(admin.getPackageName()); + } + } + + private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) { + Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner"); + Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin), + "admin is not the device owner"); + } + + @Override public void setUsbDataSignalingEnabled(String packageName, boolean enabled) { Objects.requireNonNull(packageName, "Admin package name must be provided"); final CallerIdentity caller = getCallerIdentity(packageName); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 776b44445678..257fc640f93c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -23,6 +23,8 @@ import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; +import android.os.Handler; +import android.os.Looper; import android.util.Slog; import com.android.internal.os.IResultReceiver; @@ -42,10 +44,15 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { private static final String TAG = OneTimeSafetyChecker.class.getSimpleName(); + private static final long SELF_DESTRUCT_TIMEOUT_MS = 10_000; + private final DevicePolicyManagerService mService; private final DevicePolicySafetyChecker mRealSafetyChecker; private final @DevicePolicyOperation int mOperation; private final @OperationSafetyReason int mReason; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + private boolean mDone; OneTimeSafetyChecker(DevicePolicyManagerService service, @DevicePolicyOperation int operation, @OperationSafetyReason int reason) { @@ -53,7 +60,11 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { mOperation = operation; mReason = reason; mRealSafetyChecker = service.getDevicePolicySafetyChecker(); - Slog.i(TAG, "Saving real DevicePolicySafetyChecker as " + mRealSafetyChecker); + Slog.i(TAG, "OneTimeSafetyChecker constructor: operation= " + operationToString(operation) + + ", reason=" + operationSafetyReasonToString(reason) + + ", realChecker=" + mRealSafetyChecker + + ", maxDuration=" + SELF_DESTRUCT_TIMEOUT_MS + "ms"); + mHandler.postDelayed(() -> selfDestruct(), SELF_DESTRUCT_TIMEOUT_MS); } @Override @@ -99,8 +110,21 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { } private void disableSelf() { + if (mDone) { + Slog.w(TAG, "disableSelf(): already disabled"); + return; + } Slog.i(TAG, "restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker); + mDone = true; + } + + private void selfDestruct() { + if (mDone) return; + + // Usually happens when a CTS failed before calling the DPM method that would clear it + Slog.e(TAG, "Self destructing " + this + ", as it was not automatically disabled"); + disableSelf(); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 1e70d59a5fd5..7fdd6eeef642 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -16,10 +16,13 @@ package com.android.server.devicepolicy; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; + import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AppOpsManagerInternal; +import android.app.admin.DevicePolicyManager.DeviceOwnerType; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.content.ComponentName; @@ -93,6 +96,7 @@ class Owners { private static final String TAG_PROFILE_OWNER = "profile-owner"; // Holds "context" for device-owner, this must not be show up before device-owner. private static final String TAG_DEVICE_OWNER_CONTEXT = "device-owner-context"; + private static final String TAG_DEVICE_OWNER_TYPE = "device-owner-type"; private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; @@ -109,6 +113,7 @@ class Owners { // New attribute for profile owner of organization-owned device. private static final String ATTR_PROFILE_OWNER_OF_ORG_OWNED_DEVICE = "isPoOrganizationOwnedDevice"; + private static final String ATTR_DEVICE_OWNER_TYPE_VALUE = "value"; private final UserManager mUserManager; private final UserManagerInternal mUserManagerInternal; @@ -121,6 +126,9 @@ class Owners { // Internal state for the device owner package. private OwnerInfo mDeviceOwner; + // Device owner type for a managed device. + private final ArrayMap<String, Integer> mDeviceOwnerTypes = new ArrayMap<>(); + private int mDeviceOwnerUserId = UserHandle.USER_NULL; // Internal state for the profile owner packages. @@ -334,6 +342,7 @@ class Owners { void clearDeviceOwner() { synchronized (mLock) { + mDeviceOwnerTypes.remove(mDeviceOwner.packageName); mDeviceOwner = null; mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -384,12 +393,16 @@ class Owners { void transferDeviceOwnership(ComponentName target) { synchronized (mLock) { + Integer previousDeviceOwnerType = mDeviceOwnerTypes.remove(mDeviceOwner.packageName); // We don't set a name because it's not used anyway. // See DevicePolicyManagerService#getDeviceOwnerName mDeviceOwner = new OwnerInfo(null, target, mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri, mDeviceOwner.remoteBugreportHash, /* isOrganizationOwnedDevice =*/ mDeviceOwner.isOrganizationOwnedDevice); + if (previousDeviceOwnerType != null) { + mDeviceOwnerTypes.put(mDeviceOwner.packageName, previousDeviceOwnerType); + } pushToPackageManagerLocked(); pushToActivityTaskManagerLocked(); pushToActivityManagerLocked(); @@ -596,6 +609,37 @@ class Owners { } } + void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType) { + synchronized (mLock) { + if (!hasDeviceOwner()) { + Slog.e(TAG, "Attempting to set a device owner type when there is no device owner"); + return; + } else if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) { + Slog.e(TAG, "Device owner type for " + packageName + " has already been set"); + return; + } + + mDeviceOwnerTypes.put(packageName, deviceOwnerType); + writeDeviceOwner(); + } + } + + @DeviceOwnerType + int getDeviceOwnerType(String packageName) { + synchronized (mLock) { + if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) { + return mDeviceOwnerTypes.get(packageName); + } + return DEVICE_OWNER_TYPE_DEFAULT; + } + } + + boolean isDeviceOwnerTypeSetForDeviceOwner(String packageName) { + synchronized (mLock) { + return !mDeviceOwnerTypes.isEmpty() && mDeviceOwnerTypes.containsKey(packageName); + } + } + private boolean readLegacyOwnerFileLocked(File file) { if (!file.exists()) { // Already migrated or the device has no owners. @@ -880,6 +924,16 @@ class Owners { out.startTag(null, TAG_DEVICE_OWNER_CONTEXT); out.attributeInt(null, ATTR_USERID, mDeviceOwnerUserId); out.endTag(null, TAG_DEVICE_OWNER_CONTEXT); + + } + + if (!mDeviceOwnerTypes.isEmpty()) { + for (ArrayMap.Entry<String, Integer> entry : mDeviceOwnerTypes.entrySet()) { + out.startTag(null, TAG_DEVICE_OWNER_TYPE); + out.attribute(null, ATTR_PACKAGE, entry.getKey()); + out.attributeInt(null, ATTR_DEVICE_OWNER_TYPE_VALUE, entry.getValue()); + out.endTag(null, TAG_DEVICE_OWNER_TYPE); + } } if (mSystemUpdatePolicy != null) { @@ -942,6 +996,12 @@ class Owners { } } break; + case TAG_DEVICE_OWNER_TYPE: + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); + int deviceOwnerType = parser.getAttributeInt(null, ATTR_DEVICE_OWNER_TYPE_VALUE, + DEVICE_OWNER_TYPE_DEFAULT); + mDeviceOwnerTypes.put(packageName, deviceOwnerType); + break; default: Slog.e(TAG, "Unexpected tag: " + tag); return false; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dd2dd8150165..a3d335340e9f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -104,7 +104,6 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.server.am.ActivityManagerService; import com.android.server.appbinding.AppBindingService; -import com.android.server.apphibernation.AppHibernationService; import com.android.server.attention.AttentionManagerService; import com.android.server.audio.AudioService; import com.android.server.biometrics.AuthService; @@ -163,6 +162,7 @@ import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.policy.AppOpsPolicy; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.RoleServicePlatformHelperImpl; @@ -1718,14 +1718,9 @@ public final class SystemServer implements Dumpable { startTextToSpeechManagerService(context, t); // System Speech Recognition Service - if (deviceHasConfigString(context, - R.string.config_defaultOnDeviceSpeechRecognitionService)) { - t.traceBegin("StartSpeechRecognitionManagerService"); - mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS); - t.traceEnd(); - } else { - Slog.d(TAG, "System speech recognition is not defined by OEM"); - } + t.traceBegin("StartSpeechRecognitionManagerService"); + mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS); + t.traceEnd(); // App prediction manager service if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) { @@ -1785,7 +1780,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartIpSecService"); try { - ipSecService = IpSecService.create(context, networkManagement); + ipSecService = IpSecService.create(context); ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService); } catch (Throwable e) { reportWtf("starting IpSec Service", e); @@ -2150,11 +2145,9 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); t.traceEnd(); - if (AppHibernationService.isAppHibernationEnabled()) { - t.traceBegin("StartAppHibernationService"); - mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS); - t.traceEnd(); - } + t.traceBegin("StartAppHibernationService"); + mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS); + t.traceEnd(); if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) { t.traceBegin("StartGestureLauncher"); @@ -2664,6 +2657,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("RegisterAppOpsPolicy"); + try { + mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy()); + } catch (Throwable e) { + reportWtf("registering app ops policy", e); + } + t.traceEnd(); + // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/musicrecognition/Android.bp b/services/musicrecognition/Android.bp index fea9efa9dde5..8298dec29884 100644 --- a/services/musicrecognition/Android.bp +++ b/services/musicrecognition/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.musicsearch-sources", srcs: ["java/**/*.java"], @@ -10,4 +19,4 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.musicsearch-sources"], libs: ["services.core", "app-compat-annotations"], -}
\ No newline at end of file +} diff --git a/services/net/Android.bp b/services/net/Android.bp index c68004a3591b..b01e42516358 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -52,6 +52,7 @@ java_library { libs: [ "unsupportedappusage", "framework-wifi-util-lib", + "framework-connectivity" ], static_libs: [ // All the classes in netd_aidl_interface must be jarjar so they do not conflict with the diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index cbebe6984794..2219d477630e 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -38,7 +38,6 @@ import static org.testng.Assert.expectThrows; import android.annotation.UserIdInt; import android.app.Application; -import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; @@ -874,8 +873,7 @@ public class BackupManagerServiceRoboTest { SecurityException.class, () -> backupManagerService.requestBackup( - mUserTwoId, packages, observer, monitor, 0, - OperationType.BACKUP)); + mUserTwoId, packages, observer, monitor, 0)); } /** @@ -893,11 +891,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); - verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -910,11 +906,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0); - verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -927,11 +921,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); - verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0); } /** @@ -1092,11 +1084,9 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT); - verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } /** Test that the backup service does not route methods for non-registered users. */ @@ -1106,11 +1096,9 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT); - verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } /** Test that the backup service routes methods correctly to the user that requests it. */ diff --git a/services/searchui/Android.bp b/services/searchui/Android.bp index cc632940e4a6..3081a5111ab7 100644 --- a/services/searchui/Android.bp +++ b/services/searchui/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.searchui-sources", srcs: ["java/**/*.java"], diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp index fcf780d4d927..640a88dbaa24 100644 --- a/services/smartspace/Android.bp +++ b/services/smartspace/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.smartspace-sources", srcs: ["java/**/*.java"], diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp index b7a0624e02b8..e70a734cf271 100644 --- a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_library { name: "PackageManagerServiceHostTestsIntentVerifyUtils", srcs: ["src/**/*.kt"], diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp index af0ac77eaadd..7e4f0e72b62d 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test_helper_app { name: "PackageManagerServiceDeviceSideTests", sdk_version: "test_current", diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp index e82f57d20fcc..4f3f2eb1b58e 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test_helper_app { name: "PackageManagerTestIntentVerifier", srcs: [ "src/**/*.kt" ], diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp index 7161fdd33516..9f9ed24c63bc 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test_helper_app { name: "PackageManagerTestIntentVerifierTarget1", manifest: "AndroidManifest1.xml", diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp index 58f17f253a24..ebad5afac625 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test_helper_app { name: "PackageManagerTestAppDeclaresStaticLibrary", manifest: "AndroidManifestDeclaresStaticLibrary.xml", diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp index 4aa8abc84392..334e53a3aec7 100644 --- a/services/tests/PackageManagerServiceTests/unit/Android.bp +++ b/services/tests/PackageManagerServiceTests/unit/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "PackageManagerServiceUnitTests", srcs: ["src/**/*.kt"], diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index deb314764404..9447f390ada0 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -16,8 +16,9 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationRequest +import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationUserSelection import android.os.Parcel import android.os.Parcelable @@ -27,6 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.util.UUID +import kotlin.random.Random @RunWith(Parameterized::class) class DomainVerificationCoreApiTest { @@ -40,18 +42,25 @@ class DomainVerificationCoreApiTest { assertThat(value).containsExactlyEntriesIn(other) } + private val massiveSet by lazy { + val fragmentOf21 = ".com.example.test.app" + val list = mutableListOf("prefix$fragmentOf21") + var totalSize = 0 + // Slightly overshoot a size of 1MB + while (totalSize < (1024 * 512)) { + val nextValue = "${list.last()}$fragmentOf21" + totalSize += nextValue.length + list += nextValue + } + list.toSet() + } + @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "{0}") fun parameters() = arrayOf( Parameter( - initial = { - DomainVerificationRequest( - setOf( - "com.test.pkg.one", - "com.test.pkg.two" - ) - ) - }, + testName = "DomainVerificationRequest", + initial = { DomainVerificationRequest(massiveSet) }, unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) }, assertion = { first, second -> assertAll<DomainVerificationRequest, Set<String>>(first, second, @@ -61,15 +70,12 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationInfo", initial = { DomainVerificationInfo( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", - mapOf( - "example.com" to 0, - "example.org" to 1, - "example.new" to 1000 - ) + massiveSet.withIndex().associate { it.value to it.index } ) }, unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) }, @@ -86,17 +92,15 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationUserSelection", initial = { DomainVerificationUserSelection( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", UserHandle.of(10), true, - mapOf( - "example.com" to true, - "example.org" to false, - "example.new" to true - ) + massiveSet.withIndex() + .associate { it.value to (it.index % 3) } ) }, unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) }, @@ -114,21 +118,35 @@ class DomainVerificationCoreApiTest { first, second, { it.isLinkHandlingAllowed }, { it.component4() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Map<String, Boolean>>( - first, second, { it.hostToUserSelectionMap }, + assertAll<DomainVerificationUserSelection, Map<String, Int>>( + first, second, { it.hostToStateMap }, { it.component5() }, IS_MAP_EQUAL_TO ) } + ), + Parameter( + testName = "DomainSet", + initial = { DomainSet(massiveSet) }, + unparcel = { DomainSet.CREATOR.createFromParcel(it) }, + assertion = { first, second -> + assertAll<DomainSet, Set<String>>( + first, second, + { it.domains }, assertion = IS_EQUAL_TO + ) + } ) ) class Parameter<T : Parcelable>( + val testName: String, val initial: () -> T, val unparcel: (Parcel) -> T, private val assertion: (first: T, second: T) -> Unit ) { @Suppress("UNCHECKED_CAST") fun assert(first: Any, second: Any) = assertion(first as T, second as T) + + override fun toString() = testName } private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) { @@ -141,11 +159,17 @@ class DomainVerificationCoreApiTest { first: T, second: T, fieldValue: (T) -> V, - componentValue: (T) -> V, + componentValue: ((T) -> V)? = null, assertion: (value: V, other: V) -> Unit ) { - val values = arrayOf<Any>(fieldValue(first), fieldValue(second), - componentValue(first), componentValue(second)) + val values = mutableListOf<Any>(fieldValue(first), fieldValue(second)) + .apply { + componentValue?.let { + add(it(first)) + add(it(second)) + } + } + .toTypedArray() values.indices.drop(1).forEach { @Suppress("UNCHECKED_CAST") assertion(values[0] as V, values[it] as V) 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 2d23fb4990bf..89394837655a 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 @@ -244,6 +244,14 @@ class DomainVerificationEnforcerTest { service(Type.LEGACY_QUERENT, "getLegacyUserState") { getLegacyState(it.targetPackageName, it.userId) }, + service(Type.OWNER_QUERENT, "getOwnersForDomain") { + // Re-use package name, since the result itself isn't relevant + getOwnersForDomain(it.targetPackageName) + }, + service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") { + // Re-use package name, since the result itself isn't relevant + getOwnersForDomain(it.targetPackageName, it.userId) + }, ) } @@ -327,6 +335,7 @@ class DomainVerificationEnforcerTest { domainSetId ) ) { + whenever(getName()) { packageName } whenever(getPkg()) { mockPkg(packageName) } whenever(this.domainSetId) { domainSetId } whenever(userState) { @@ -357,6 +366,8 @@ class DomainVerificationEnforcerTest { Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) Type.LEGACY_QUERENT -> legacyQuerent() Type.LEGACY_SELECTOR -> legacyUserSelector() + Type.OWNER_QUERENT -> ownerQuerent(verifyCrossUser = false) + Type.OWNER_QUERENT_USER -> ownerQuerent(verifyCrossUser = true) }.run { /*exhaust*/ } } @@ -628,6 +639,80 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = false) } + private fun ownerQuerent(verifyCrossUser: Boolean) { + val allowQueryAll = AtomicBoolean(false) + val allowUserSelection = AtomicBoolean(false) + val allowInteractAcrossUsers = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowQueryAll, + android.Manifest.permission.QUERY_ALL_PACKAGES + ) + initPermission( + allowUserSelection, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + ) + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Owner querent makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } + + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(true) + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowUserSelection.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(false) + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(true) + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + private fun Context.initPermission(boolean: AtomicBoolean, permission: String) { whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) { if (!boolean.get()) { @@ -694,6 +779,12 @@ class DomainVerificationEnforcerTest { LEGACY_QUERENT, // Holding the legacy preferred apps permission - LEGACY_SELECTOR + LEGACY_SELECTOR, + + // Holding user setting permission, but not targeting a package + OWNER_QUERENT, + + // Holding user setting permission, but not targeting a package, but targeting cross user + OWNER_QUERENT_USER, } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt new file mode 100644 index 000000000000..48056a2b54d1 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt @@ -0,0 +1,174 @@ +/* + * 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.pm.test.verify.domain + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import androidx.test.InstrumentationRegistry +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID + +class DomainVerificationManagerUserSelectionOverrideTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + + private val DOMAIN_ONE = + DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName + + private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE + private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED + private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED + } + + private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) + private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) + + fun makeManager(): DomainVerificationManager = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 } + whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 } + whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() } + whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() } + }) + addPackage(pkg1) + addPackage(pkg2) + + // Starting state for all tests is to have domain 1 enabled for the first package + setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true) + + assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + } + + fun mockPkgSetting(pkgName: String, domainSetId: UUID) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(DOMAIN_ONE, null) + } + ) + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL) + addDataAuthority("example2.com", null) + } + ) + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } + + @Test + fun anotherPackageTakeoverSuccess() { + val manager = makeManager() + + // Attempt override by package 2 + manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + + // 1 loses approval + assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) + + // 2 gains approval + assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + + // 2 is the only owner + assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + .containsExactly(PKG_TWO) + } + + @Test(expected = IllegalArgumentException::class) + fun anotherPackageTakeoverFailure() { + val manager = makeManager() + + // Verify 1 to give it a higher approval level + manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), + DomainVerificationManager.STATE_SUCCESS) + assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) + assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + .containsExactly(PKG_ONE) + + // Attempt override by package 2 + manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + } + + private fun DomainVerificationManager.stateFor(pkgName: String, host: String) = + getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host] +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index a76d8cee582c..439048ce51bb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -34,7 +34,7 @@ operator fun DomainVerificationUserSelection.component1() = identifier operator fun DomainVerificationUserSelection.component2() = packageName operator fun DomainVerificationUserSelection.component3() = user operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed -operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap +operator fun DomainVerificationUserSelection.component5() = hostToStateMap operator fun DomainVerificationPersistence.ReadResult.component1() = active operator fun DomainVerificationPersistence.ReadResult.component2() = restored 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 48518f4693dd..010eacf3f51f 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 @@ -237,6 +237,7 @@ class DomainVerificationSettingsMutationTest { TEST_UUID ) ) { + whenever(getName()) { TEST_PKG } whenever(getPkg()) { mockPkg() } whenever(domainSetId) { TEST_UUID } whenever(userState) { diff --git a/services/tests/inprocesstests/Android.bp b/services/tests/inprocesstests/Android.bp index 6dd059fc919d..7c237ac6befb 100644 --- a/services/tests/inprocesstests/Android.bp +++ b/services/tests/inprocesstests/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "FrameworksInProcessTests", srcs: ["src/**/*.java"], diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index 928065a7ebd9..a32bf2c3b260 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + cc_library_shared { name: "libactivitymanagermockingservicestestjni", diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index 56d30ccdf59f..20a58426f1eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -139,7 +139,8 @@ public final class CachedAppOptimizerTest { app.info.uid = packageUid; // Exact value does not mater, it can be any state for which compaction is allowed. app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.mState.setSetAdj(905); + app.mState.setSetAdj(899); + app.mState.setCurAdj(940); return app; } @@ -164,8 +165,6 @@ public final class CachedAppOptimizerTest { CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE); assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo( CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE); - assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( - CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( @@ -176,6 +175,11 @@ public final class CachedAppOptimizerTest { CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isEqualTo( CachedAppOptimizer.DEFAULT_USE_FREEZER); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ); + Set<Integer> expected = new HashSet<>(); for (String s : TextUtils.split( @@ -231,6 +235,14 @@ public final class CachedAppOptimizerTest { Long.toString( CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, + Long.toString( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 10), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, + Long.toString( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ - 10), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isEqualTo( CachedAppOptimizer.DEFAULT_USE_FREEZER); @@ -261,6 +273,12 @@ public final class CachedAppOptimizerTest { CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 10); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ - 10); assertThat(mCachedAppOptimizerUnderTest.mCompactStatsdSampleRate).isEqualTo( CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo( @@ -437,7 +455,7 @@ public final class CachedAppOptimizerTest { mCachedAppOptimizerUnderTest.init(); // When we override new reasonable throttle values after init... - mCountDown = new CountDownLatch(6); + mCountDown = new CountDownLatch(8); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false); @@ -456,7 +474,13 @@ public final class CachedAppOptimizerTest { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ - 1), false); + assertThat(mCountDown.await(7, TimeUnit.SECONDS)).isTrue(); // Then those flags values are reflected in the compactor. assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( @@ -471,6 +495,10 @@ public final class CachedAppOptimizerTest { CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ - 1); } @Test @@ -902,7 +930,6 @@ public final class CachedAppOptimizerTest { valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter3); - } @Test @@ -954,6 +981,54 @@ public final class CachedAppOptimizerTest { assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter); } + @Test + public void processWithOomAdjTooSmall_notFullCompacted() throws Exception { + // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set Min and + // Max OOM_Adj throttles. + mCachedAppOptimizerUnderTest.init(); + setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); + setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, Long.toString(920), true); + setFlag(CachedAppOptimizer.KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, Long.toString(950), true); + initActivityManagerService(); + + // Simulate RSS memory for which compaction should occur. + long[] rssBefore = + new long[]{/*Total RSS*/ 15000, /*File RSS*/ 15000, /*Anon RSS*/ 15000, + /*Swap*/ 10000}; + long[] rssAfter = + new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000}; + // Process that passes properties. + int pid = 1; + ProcessRecord processRecord = + makeProcessRecord(pid, 2, 3, "p1", "app1"); + mProcessDependencies.setRss(rssBefore); + mProcessDependencies.setRssAfterCompaction(rssAfter); + + // Compaction should occur if (setAdj < min for process || setAdj > max for process) && + // (MIN < curAdj < MAX) + // GIVEN OomAdj score below threshold. + processRecord.mState.setSetAdj(899); + processRecord.mState.setCurAdj(970); + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS NOT compacted. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull(); + + // GIVEN (setAdj < MIN || setAdj > MAX) && (MIN < curAdj < MAX) + processRecord.mState.setSetAdj(910); + processRecord.mState.setCurAdj(930); + // WHEN we try to run compaction + mCachedAppOptimizerUnderTest.compactAppFull(processRecord); + waitForHandler(); + // THEN process IS compacted. + assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); + long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats + .get(pid) + .getRssAfterCompaction(); + assertThat(valuesAfter).isEqualTo(rssAfter); + } + private void setFlag(String key, String value, boolean defaultValue) throws Exception { mCountDown = new CountDownLatch(1); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index a382e85538cd..1c45203bb3c9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -46,6 +46,7 @@ import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; import static com.android.server.am.ProcessList.HOME_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; @@ -877,6 +878,39 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoOne_Service_MediumPerceptible() { + { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); + + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj()); + } + + { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + WindowProcessController wpc = client.getWindowProcessController(); + doReturn(true).when(wpc).isHeavyWeightProcess(); + bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); + doReturn(false).when(wpc).isHeavyWeightProcess(); + + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj()); + } + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoOne_Service_Other() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 728b97cc3968..18184b0a82af 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -27,11 +27,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -44,6 +47,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.lights.LightsManager; @@ -98,6 +102,9 @@ public class LocalDisplayAdapterTest { @Mock private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; + private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; + private static final int[] BACKLIGHT_RANGE = { 1, 255 }; + private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; @Before public void setUp() throws Exception { @@ -114,6 +121,18 @@ public class LocalDisplayAdapterTest { mListener, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); + + TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS); + when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits)) + .thenReturn(mockNitsRange); + when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight)) + .thenReturn(BACKLIGHT_RANGE); + when(mMockedResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingMinimumFloat)) + .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]); + when(mMockedResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingMaximumFloat)) + .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]); } @After @@ -629,13 +648,13 @@ public class LocalDisplayAdapterTest { // Test as default display BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.514f); + ba.setBacklight(0.514f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f); // Test as not default display BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); - ba2.setBrightness(0.323f); + ba2.setBacklight(0.323f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f); } @@ -648,7 +667,7 @@ public class LocalDisplayAdapterTest { BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.123f); + ba.setBacklight(0.123f); verify(mMockedBacklight).setBrightness(0.123f); } @@ -661,7 +680,7 @@ public class LocalDisplayAdapterTest { BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.456f); + ba.setBacklight(0.456f); // Adapter does not forward any brightness in this case. verify(mMockedBacklight, never()).setBrightness(anyFloat()); @@ -864,4 +883,23 @@ public class LocalDisplayAdapterTest { } } + private TypedArray createFloatTypedArray(float[] vals) { + TypedArray mockArray = mock(TypedArray.class); + when(mockArray.length()).thenAnswer(invocation -> { + return vals.length; + }); + when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> { + final float def = (float) invocation.getArguments()[1]; + if (vals == null) { + return def; + } + int idx = (int) invocation.getArguments()[0]; + if (idx >= 0 && idx < vals.length) { + return vals[idx]; + } else { + return def; + } + }); + return mockArray; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 775276bd03f6..f7f592886473 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.job.controllers; 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_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -611,6 +612,7 @@ public class ConnectivityControllerTest { private static NetworkCapabilities createCapabilities() { return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_VALIDATED); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 88a691bbc209..bf95f4c51d96 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -31,6 +31,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import static com.android.server.job.JobSchedulerService.sSystemClock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -58,6 +59,7 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IUidObserver; import android.app.job.JobInfo; +import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; @@ -85,6 +87,7 @@ import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import com.android.server.job.controllers.QuotaController.ExecutionStats; import com.android.server.job.controllers.QuotaController.QcConstants; +import com.android.server.job.controllers.QuotaController.ShrinkableDebits; import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.usage.AppStandbyInternal; @@ -125,6 +128,7 @@ public class QuotaControllerTest { private int mSourceUid; private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener; private IUidObserver mUidObserver; + private UsageStatsManagerInternal.UsageEventListener mUsageEventListener; DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private MockitoSession mMockingSession; @@ -218,6 +222,8 @@ public class QuotaControllerTest { ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class); + ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor = + ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class); mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); @@ -229,6 +235,8 @@ public class QuotaControllerTest { verify(mPowerAllowlistInternal) .registerTempAllowlistChangeListener(taChangeCaptor.capture()); mTempAllowlistListener = taChangeCaptor.getValue(); + verify(mUsageStatsManager).registerListener(ueListenerCaptor.capture()); + mUsageEventListener = ueListenerCaptor.getValue(); try { verify(activityManager).registerUidObserver( uidObserverCaptor.capture(), @@ -288,7 +296,7 @@ public class QuotaControllerTest { verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid)); assertFalse(foregroundUids.get(uid)); } - waitForQuietBackground(); + waitForNonDelayedMessagesProcessed(); } catch (Exception e) { fail("exception encountered: " + e.getMessage()); } @@ -385,13 +393,8 @@ public class QuotaControllerTest { } } - private void waitForQuietBackground() throws Exception { - for (int i = 0; i < 5; ++i) { - if (!mQuotaController.isActiveBackgroundProcessing()) { - break; - } - Thread.sleep(500); - } + private void waitForNonDelayedMessagesProcessed() { + mQuotaController.getHandler().runWithScissors(() -> {}, 15_000); } @Test @@ -1330,7 +1333,9 @@ public class QuotaControllerTest { timeUsedMs, 5), true); JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0); setStandbyBucket(RARE_INDEX, job); - mQuotaController.maybeStartTrackingJobLocked(job, null); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(job, null); + } setCharging(); synchronized (mQuotaController.mLock) { @@ -5158,4 +5163,257 @@ public class QuotaControllerTest { eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); } + + @Test + public void testEJDebitTallying() { + setStandbyBucket(RARE_INDEX); + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); + // 15 seconds for each 30 second chunk. + setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS); + + // No history. Debits should be 0. + ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); + assertEquals(0, debit.getTallyLocked()); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Regular job shouldn't affect EJ tally. + JobStatus regJob = createJobStatus("testEJDebitTallying", 1); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(regJob, null); + mQuotaController.prepareForExecutionLocked(regJob); + } + advanceElapsedClock(5000); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStopTrackingJobLocked(regJob, null, false); + } + assertEquals(0, debit.getTallyLocked()); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // EJ job should affect EJ tally. + JobStatus eJob = createExpeditedJobStatus("testEJDebitTallying", 2); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(eJob, null); + mQuotaController.prepareForExecutionLocked(eJob); + } + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStopTrackingJobLocked(eJob, null, false); + } + assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Instantaneous event for a different user shouldn't affect tally. + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); + + UsageEvents.Event event = + new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID + 10, event); + assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + + // Instantaneous event for correct user should reduce tally. + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(6 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Activity start shouldn't reduce tally, but duration with activity started should affect + // remaining EJ time. + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + advanceElapsedClock(30 * SECOND_IN_MILLIS); + assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(6 * MINUTE_IN_MILLIS + 15 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + advanceElapsedClock(30 * SECOND_IN_MILLIS); + assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(6 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // With activity pausing/stopping/destroying, tally should be updated. + advanceElapsedClock(MINUTE_IN_MILLIS); + event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(3 * MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + @Test + public void testEJDebitTallying_StaleSession() { + setStandbyBucket(RARE_INDEX); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); + + final long nowElapsed = sElapsedRealtimeClock.millis(); + TimingSession ts = new TimingSession(nowElapsed, nowElapsed + 10 * MINUTE_IN_MILLIS, 5); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); + + // Make the session stale. + advanceElapsedClock(12 * MINUTE_IN_MILLIS + mQcConstants.EJ_WINDOW_SIZE_MS); + + // With lazy deletion, we don't update the tally until getRemainingEJExecutionTimeLocked() + // is called, so call that first. + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); + assertEquals(0, debit.getTallyLocked()); + } + + /** + * Tests that rewards are properly accounted when there's no EJ running and the rewards exceed + * the accumulated debits. + */ + @Test + public void testEJDebitTallying_RewardExceedDebits_NoActiveSession() { + setStandbyBucket(WORKING_INDEX); + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); + + final long nowElapsed = sElapsedRealtimeClock.millis(); + TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS, + nowElapsed - 4 * MINUTE_IN_MILLIS, 2); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); + + ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); + assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(29 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(30 * SECOND_IN_MILLIS); + UsageEvents.Event event = + new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(0, debit.getTallyLocked()); + assertEquals(30 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(MINUTE_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + assertEquals(30 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Excessive rewards don't increase maximum quota. + event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(0, debit.getTallyLocked()); + assertEquals(30 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + /** + * Tests that rewards are properly accounted when there's an active EJ running and the rewards + * exceed the accumulated debits. + */ + @Test + public void testEJDebitTallying_RewardExceedDebits_ActiveSession() { + setStandbyBucket(WORKING_INDEX); + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); + // 15 seconds for each 30 second chunk. + setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS); + + final long nowElapsed = sElapsedRealtimeClock.millis(); + TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS, + nowElapsed - 4 * MINUTE_IN_MILLIS, 2); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); + + ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); + assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(29 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // With rewards coming in while an EJ is running, the remaining execution time should be + // adjusted accordingly (decrease due to EJ running + increase from reward). + JobStatus eJob = + createExpeditedJobStatus("testEJDebitTallying_RewardExceedDebits_ActiveSession", 1); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(eJob, null); + mQuotaController.prepareForExecutionLocked(eJob); + } + advanceElapsedClock(30 * SECOND_IN_MILLIS); + assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); + assertEquals(28 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(30 * SECOND_IN_MILLIS); + UsageEvents.Event event = + new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(0, debit.getTallyLocked()); + assertEquals(29 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(MINUTE_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + assertEquals(28 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Activity start shouldn't reduce tally, but duration with activity started should affect + // remaining EJ time. + event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + advanceElapsedClock(30 * SECOND_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + // Decrease by 30 seconds for running EJ, increase by 15 seconds due to ongoing activity. + assertEquals(27 * MINUTE_IN_MILLIS + 45 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + advanceElapsedClock(30 * SECOND_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(MINUTE_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + assertEquals(27 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(0, debit.getTallyLocked()); + assertEquals(28 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(MINUTE_IN_MILLIS); + assertEquals(0, debit.getTallyLocked()); + assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // At this point, with activity pausing/stopping/destroying, since we're giving a reward, + // tally should remain 0, and time remaining shouldn't change since it was accounted for + // at every step. + event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis()); + event.mPackage = SOURCE_PACKAGE; + mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); + waitForNonDelayedMessagesProcessed(); + assertEquals(0, debit.getTallyLocked()); + assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 66b037d70a40..c4c9ad088e45 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -123,9 +123,10 @@ public class LocationProviderManagerTest { .setPowerUsage(POWER_USAGE_HIGH) .setAccuracy(ProviderProperties.ACCURACY_FINE) .build(); + private static final CallerIdentity PROVIDER_IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution"); private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1, - "mypackage", - "attribution"); + "mypackage", "attribution", "listener"); private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid()); private Random mRandom; @@ -169,7 +170,7 @@ public class LocationProviderManagerTest { mPassive.startManager(); mPassive.setRealProvider(new PassiveLocationProvider(mContext)); - mProvider = new TestProvider(PROPERTIES, IDENTITY); + mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY); mProvider.setProviderAllowed(true); mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive); @@ -351,7 +352,8 @@ public class LocationProviderManagerTest { @Test public void testGetLastLocation_ClearOnMockRemoval() { - MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, IDENTITY); + MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY, + null); mockProvider.setAllowed(true); mManager.setMockProvider(mockProvider); @@ -441,7 +443,7 @@ public class LocationProviderManagerTest { @Test public void testRegisterListener_SameProcess() throws Exception { CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage", - "attribution"); + "attribution", "listener"); ILocationListener listener = createMockLocationListener(); mManager.registerLocationRequest( @@ -477,7 +479,7 @@ public class LocationProviderManagerTest { @Test public void testRegisterListener_Unregister_SameProcess() throws Exception { CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage", - "attribution"); + "attribution", "listener"); ILocationListener listener = createMockLocationListener(); mManager.registerLocationRequest( @@ -604,7 +606,7 @@ public class LocationProviderManagerTest { @Test public void testRegisterListener_Wakelock() throws Exception { CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage", - "attribution"); + "attribution", "listener"); ILocationListener listener = createMockLocationListener(); mManager.registerLocationRequest( @@ -1047,7 +1049,7 @@ public class LocationProviderManagerTest { private final ArrayList<Runnable> mFlushCallbacks = new ArrayList<>(); TestProvider(ProviderProperties properties, CallerIdentity identity) { - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, null); } public void setProviderAllowed(boolean allowed) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index 07170dacb4da..e8a0bb51e20f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -71,7 +71,8 @@ public class MockableLocationProviderTest { .setPowerUsage(POWER_USAGE_LOW) .setAccuracy(ACCURACY_FINE) .build(), - CallerIdentity.forTest(0, 1, "testpackage", "test")); + CallerIdentity.forTest(0, 1, "testpackage", "test"), + null); mProvider = new MockableLocationProvider(lock); mProvider.getController().setListener(mListener); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 775bdd580157..a1eadbe4a64f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -40,7 +40,7 @@ public class FakeProvider extends AbstractLocationProvider { private final FakeProviderInterface mFakeInterface; public FakeProvider(FakeProviderInterface fakeInterface) { - super(Runnable::run, null, null); + super(Runnable::run, null, null, null); mFakeInterface = fakeInterface; } diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java index 488e5cdf33b9..1870df9ecf17 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java @@ -30,6 +30,7 @@ import com.android.internal.os.BatteryStatsImpl; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +60,7 @@ public final class BatteryStatsServiceTest { } @Test + @Ignore("b/180015146") public void testAwaitCompletion() throws Exception { final CountDownLatch readyLatch = new CountDownLatch(2); final CountDownLatch startLatch = new CountDownLatch(1); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 1328b91d03f9..07f67327b2bf 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -110,6 +110,8 @@ public final class AppHibernationServiceTest { UserInfo userInfo = addUser(USER_ID_1); mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); + + mAppHibernationService.mIsServiceEnabled = true; } @Test 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 73b0105210c4..6890ed1688bb 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 @@ -28,6 +28,7 @@ import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; +import android.util.ArrayMap; import android.util.ArraySet; import androidx.test.core.app.ApplicationProvider; @@ -55,6 +56,7 @@ import org.junit.rules.TemporaryFolder; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; public class AppSearchImplTest { @@ -971,21 +973,46 @@ public class AppSearchImplTest { } @Test - public void testHasSchemaType() throws Exception { - // Nothing exists yet - assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isFalse(); + public void testGetPackageToDatabases() throws Exception { + Map<String, Set<String>> existingMapping = mAppSearchImpl.getPackageToDatabases(); + Map<String, Set<String>> expectedMapping = new ArrayMap<>(); + expectedMapping.putAll(existingMapping); + // Has database1 + expectedMapping.put("package1", ImmutableSet.of("database1")); mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), + "package1", + "database1", + Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), /*forceOverride=*/ false); - assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isTrue(); + assertThat(mAppSearchImpl.getPackageToDatabases()) + .containsExactlyEntriesIn(expectedMapping); - assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "UnknownSchema")) - .isFalse(); + // Has both databases + expectedMapping.put("package1", ImmutableSet.of("database1", "database2")); + mAppSearchImpl.setSchema( + "package1", + "database2", + Collections.singletonList(new AppSearchSchema.Builder("schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat(mAppSearchImpl.getPackageToDatabases()) + .containsExactlyEntriesIn(expectedMapping); + + // Has both packages + expectedMapping.put("package2", ImmutableSet.of("database1")); + mAppSearchImpl.setSchema( + "package2", + "database1", + Collections.singletonList(new AppSearchSchema.Builder("schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat(mAppSearchImpl.getPackageToDatabases()) + .containsExactlyEntriesIn(expectedMapping); } @Test diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java new file mode 100644 index 000000000000..4308885faaad --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java @@ -0,0 +1,124 @@ +/* + * 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 com.android.server.appsearch.external.localstorage.stats; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public class AppSearchStatsTest { + static final String TEST_PACKAGE_NAME = "com.google.test"; + static final String TEST_DATA_BASE = "testDataBase"; + static final int TEST_STATUS_CODE = 2; + static final int TEST_TOTAL_LATENCY_MILLIS = 20; + + @Test + public void testAppSearchStats_GeneralStats() { + final GeneralStats gStats = + new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) + .setStatusCode(TEST_STATUS_CODE) + .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) + .build(); + + assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME); + assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE); + assertThat(gStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE); + assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); + } + + @Test + public void testAppSearchStats_CallStats() { + final int estimatedBinderLatencyMillis = 1; + final int numOperationsSucceeded = 2; + final int numOperationsFailed = 3; + + final GeneralStats gStats = + new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) + .setStatusCode(TEST_STATUS_CODE) + .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) + .build(); + final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS; + final CallStats cStats = + new CallStats.Builder(gStats) + .setCallType(callType) + .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) + .setNumOperationsSucceeded(numOperationsSucceeded) + .setNumOperationsFailed(numOperationsFailed) + .build(); + + assertThat(cStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME); + assertThat(cStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE); + assertThat(cStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE); + assertThat(cStats.getGeneralStats().getTotalLatencyMillis()) + .isEqualTo(TEST_TOTAL_LATENCY_MILLIS); + assertThat(cStats.getEstimatedBinderLatencyMillis()) + .isEqualTo(estimatedBinderLatencyMillis); + assertThat(cStats.getCallType()).isEqualTo(callType); + assertThat(cStats.getNumOperationsSucceeded()).isEqualTo(numOperationsSucceeded); + assertThat(cStats.getNumOperationsFailed()).isEqualTo(numOperationsFailed); + } + + @Test + public void testAppSearchStats_PutDocumentStats() { + final int generateDocumentProtoLatencyMillis = 1; + final int rewriteDocumentTypesLatencyMillis = 2; + final int nativeLatencyMillis = 3; + final int nativeDocumentStoreLatencyMillis = 4; + final int nativeIndexLatencyMillis = 5; + final int nativeIndexMergeLatencyMillis = 6; + final int nativeDocumentSize = 7; + final int nativeNumTokensIndexed = 8; + final int nativeNumTokensClipped = 9; + + final GeneralStats gStats = + new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) + .setStatusCode(TEST_STATUS_CODE) + .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) + .build(); + final PutDocumentStats pStats = + new PutDocumentStats.Builder(gStats) + .setGenerateDocumentProtoLatencyMillis(generateDocumentProtoLatencyMillis) + .setRewriteDocumentTypesLatencyMillis(rewriteDocumentTypesLatencyMillis) + .setNativeLatencyMillis(nativeLatencyMillis) + .setNativeDocumentStoreLatencyMillis(nativeDocumentStoreLatencyMillis) + .setNativeIndexLatencyMillis(nativeIndexLatencyMillis) + .setNativeIndexMergeLatencyMillis(nativeIndexMergeLatencyMillis) + .setNativeDocumentSizeBytes(nativeDocumentSize) + .setNativeNumTokensIndexed(nativeNumTokensIndexed) + .setNativeNumTokensClipped(nativeNumTokensClipped) + .build(); + + assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME); + assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE); + assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE); + assertThat(pStats.getGeneralStats().getTotalLatencyMillis()) + .isEqualTo(TEST_TOTAL_LATENCY_MILLIS); + assertThat(pStats.getGenerateDocumentProtoLatencyMillis()) + .isEqualTo(generateDocumentProtoLatencyMillis); + assertThat(pStats.getRewriteDocumentTypesLatencyMillis()) + .isEqualTo(rewriteDocumentTypesLatencyMillis); + assertThat(pStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis); + assertThat(pStats.getNativeDocumentStoreLatencyMillis()) + .isEqualTo(nativeDocumentStoreLatencyMillis); + assertThat(pStats.getNativeIndexLatencyMillis()).isEqualTo(nativeIndexLatencyMillis); + assertThat(pStats.getNativeIndexMergeLatencyMillis()) + .isEqualTo(nativeIndexMergeLatencyMillis); + assertThat(pStats.getNativeDocumentSizeBytes()).isEqualTo(nativeDocumentSize); + assertThat(pStats.getNativeNumTokensIndexed()).isEqualTo(nativeNumTokensIndexed); + assertThat(pStats.getNativeNumTokensClipped()).isEqualTo(nativeNumTokensClipped); + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index b98f0257d7b7..205ff300c543 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import android.app.backup.BackupAgent; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -30,6 +31,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import com.android.internal.backup.IBackupTransport; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -56,6 +58,7 @@ public class UserBackupManagerServiceTest { @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @Mock TransportClient mTransportClient; + @Mock IBackupTransport mBackupTransport; @Mock BackupEligibilityRules mBackupEligibilityRules; @@ -132,6 +135,33 @@ public class UserBackupManagerServiceTest { assertThat(params.mBackupEligibilityRules).isEqualTo(mBackupEligibilityRules); } + @Test + public void testGetOperationTypeFromTransport_returnsBackupByDefault() + throws Exception { + when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); + when(mBackupTransport.getTransportFlags()).thenReturn(0); + + int operationType = mService.getOperationTypeFromTransport(mTransportClient); + + assertThat(operationType).isEqualTo(OperationType.BACKUP); + } + + @Test + public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport() + throws Exception { + // This is a temporary flag to control the new behaviour until it's ready to be fully + // rolled out. + mService.shouldUseNewBackupEligibilityRules = true; + + when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); + when(mBackupTransport.getTransportFlags()).thenReturn( + BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER); + + int operationType = mService.getOperationTypeFromTransport(mTransportClient); + + assertThat(operationType).isEqualTo(OperationType.MIGRATION); + } + private static PackageInfo getPackageInfo(String packageName) { PackageInfo packageInfo = new PackageInfo(); packageInfo.applicationInfo = new ApplicationInfo(); @@ -141,6 +171,7 @@ public class UserBackupManagerServiceTest { private static class TestBackupService extends UserBackupManagerService { boolean isEnabledStatePersisted = false; + boolean shouldUseNewBackupEligibilityRules = false; TestBackupService(Context context, PackageManager packageManager) { super(context, packageManager); @@ -158,5 +189,10 @@ public class UserBackupManagerServiceTest { @Override void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {} + + @Override + boolean shouldUseNewBackupEligibilityRules() { + return shouldUseNewBackupEligibilityRules; + } } } diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java index 2cbc3f381909..a694d5e37566 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java @@ -153,4 +153,25 @@ public class SyncOperationTest extends AndroidTestCase { assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis); assertEquals("Flex not restored", periodic.flexMillis, oneoff.flexMillis); } + + @SmallTest + public void testScheduleAsEjIsInExtras() { + Account account1 = new Account("account1", "type1"); + Bundle b1 = new Bundle(); + b1.putBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, true); + + SyncOperation op1 = new SyncOperation(account1, 0, 1, "foo", 0, + SyncOperation.REASON_USER_START, "authority1", b1, false, + ContentResolver.SYNC_EXEMPTION_NONE); + assertTrue(op1.isScheduledAsExpeditedJob()); + + PersistableBundle pb = op1.toJobInfoExtras(); + assertTrue("EJ extra not found in job extras", + ((PersistableBundle) pb.get("syncExtras")) + .containsKey(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB)); + + SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb); + assertTrue("EJ extra not found in extras", op2.getClonedExtras() + .getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB)); + } } 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 6add8d18aa3e..87100a63e35e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -20,6 +20,8 @@ import static android.app.Notification.EXTRA_TITLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; @@ -826,7 +828,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} */ @Test - public void testForceRemoveActiveAdmin() throws Exception { + public void testForceRemoveActiveAdmin_nonShellCaller() throws Exception { mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); // Add admin. @@ -840,8 +842,53 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Calling from a non-shell uid should fail with a SecurityException mContext.binder.callingUid = 123456; assertExpectException(SecurityException.class, - /* messageRegex =*/ "Non-shell user attempted to call", + /* messageRegex = */ null, () -> dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE)); + } + + /** + * Test for: + * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} + */ + @Test + public void testForceRemoveActiveAdmin_nonShellCallerWithPermission() throws Exception { + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin. + setupPackageInPackageManager(admin1.getPackageName(), + /* userId= */ CALLER_USER_HANDLE, + /* appId= */ 10138, + /* flags= */ ApplicationInfo.FLAG_TEST_ONLY); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertThat(dpm.isAdminActive(admin1)).isTrue(); + + mContext.binder.callingUid = 123456; + mContext.callerPermissions.add( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE); + + mContext.callerPermissions.add(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + // Verify + assertThat(dpm.isAdminActiveAsUser(admin1, CALLER_USER_HANDLE)).isFalse(); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, CALLER_USER_HANDLE); + } + + /** + * Test for: + * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} + */ + @Test + public void testForceRemoveActiveAdmin_ShellCaller() throws Exception { + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin. + setupPackageInPackageManager(admin1.getPackageName(), + /* userId= */ CALLER_USER_HANDLE, + /* appId= */ 10138, + /* flags= */ ApplicationInfo.FLAG_TEST_ONLY); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertThat(dpm.isAdminActive(admin1)).isTrue(); mContext.binder.callingUid = Process.SHELL_UID; dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE); @@ -7028,6 +7075,71 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testSetDeviceOwnerType_throwsExceptionWhenCallerNotAuthorized() { + assertThrows(SecurityException.class, + () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT)); + } + + @Test + public void testSetDeviceOwnerType_throwsExceptionWhenThereIsNoDeviceOwner() { + mContext.binder.clearCallingIdentity(); + assertThrows(IllegalStateException.class, + () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT)); + } + + @Test + public void testSetDeviceOwnerType_throwsExceptionWhenNotAsDeviceOwnerAdmin() throws Exception { + setDeviceOwner(); + + assertThrows(IllegalStateException.class, + () -> dpm.setDeviceOwnerType(admin2, DEVICE_OWNER_TYPE_FINANCED)); + } + + @Test + public void testSetDeviceOwnerType_asDeviceOwner_toFinancedDevice() throws Exception { + setDeviceOwner(); + + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); + assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); + assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); + + initializeDpms(); + + returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); + assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); + assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); + } + + @Test + public void testSetDeviceOwnerType_asDeviceOwner_throwsExceptionWhenSetDeviceOwnerTypeAgain() + throws Exception { + setDeviceOwner(); + + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); + assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); + assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); + + assertThrows(IllegalStateException.class, + () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT)); + } + + @Test + public void testGetDeviceOwnerType_throwsExceptionWhenThereIsNoDeviceOwner() { + assertThrows(IllegalStateException.class, () -> dpm.getDeviceOwnerType(admin1)); + } + + @Test + public void testGetDeviceOwnerType_throwsExceptionWhenNotAsDeviceOwnerAdmin() throws Exception { + setDeviceOwner(); + + assertThrows(IllegalStateException.class, () -> dpm.getDeviceOwnerType(admin2)); + } + + @Test public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() { assertThrows(SecurityException.class, () -> dpm.setUsbDataSignalingEnabled(true)); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index bfe183cc608b..39ca925d0115 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -16,6 +16,9 @@ package com.android.server.devicepolicy; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; + import static com.google.common.truth.Truth.assertThat; import android.content.ComponentName; @@ -35,7 +38,6 @@ import org.junit.runner.RunWith; * <p>Run this test with: * * {@code atest FrameworksServicesTests:com.android.server.devicepolicy.OwnersTest} - * */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -67,6 +69,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); assertThat(owners.getProfileOwnerKeys()).isEmpty(); @@ -75,6 +79,12 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)).isFalse(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)).isFalse(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse(); + + owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), + DEVICE_OWNER_TYPE_FINANCED); + // There is no device owner, so the default owner type should be returned. + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); } // Then re-read and check. @@ -84,6 +94,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); assertThat(owners.getProfileOwnerKeys()).isEmpty(); @@ -122,6 +134,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getDeviceOwnerName()).isEqualTo(null); assertThat(owners.getDeviceOwnerPackageName()).isEqualTo("com.google.android.testdpc"); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_SYSTEM); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); assertThat(owners.getProfileOwnerKeys()).isEmpty(); @@ -142,6 +156,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getDeviceOwnerName()).isEqualTo(null); assertThat(owners.getDeviceOwnerPackageName()).isEqualTo("com.google.android.testdpc"); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_SYSTEM); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); assertThat(owners.getProfileOwnerKeys()).isEmpty(); @@ -180,6 +196,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); assertThat(owners.getSystemUpdatePolicy()).isNull(); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getProfileOwnerKeys()).hasSize(2); assertThat(owners.getProfileOwnerComponent(10)) @@ -208,6 +226,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); assertThat(owners.getSystemUpdatePolicy()).isNull(); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getProfileOwnerKeys()).hasSize(2); assertThat(owners.getProfileOwnerComponent(10)) @@ -260,6 +280,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getDeviceOwnerName()).isEqualTo(null); assertThat(owners.getDeviceOwnerPackageName()).isEqualTo("com.google.android.testdpc"); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_SYSTEM); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNotNull(); assertThat(owners.getSystemUpdatePolicy().getPolicyType()).isEqualTo(5); @@ -292,6 +314,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getDeviceOwnerName()).isEqualTo(null); assertThat(owners.getDeviceOwnerPackageName()).isEqualTo("com.google.android.testdpc"); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_SYSTEM); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNotNull(); assertThat(owners.getSystemUpdatePolicy().getPolicyType()).isEqualTo(5); @@ -315,12 +339,21 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse(); owners.setDeviceOwnerUserRestrictionsMigrated(); + + owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), + DEVICE_OWNER_TYPE_FINANCED); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_FINANCED); } { final OwnersTestable owners = new OwnersTestable(getServices()); owners.load(); + assertThat(owners.hasDeviceOwner()).isTrue(); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_FINANCED); + assertThat(owners.getDeviceOwnerUserRestrictionsNeedsMigration()).isFalse(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)).isTrue(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)).isTrue(); @@ -328,12 +361,22 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse(); owners.setProfileOwnerUserRestrictionsMigrated(11); + + owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), + DEVICE_OWNER_TYPE_DEFAULT); + // The previous device owner type should persist. + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_FINANCED); } { final OwnersTestable owners = new OwnersTestable(getServices()); owners.load(); + assertThat(owners.hasDeviceOwner()).isTrue(); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_FINANCED); + assertThat(owners.getDeviceOwnerUserRestrictionsNeedsMigration()).isFalse(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)).isTrue(); assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)).isFalse(); @@ -369,6 +412,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); @@ -388,6 +433,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getSystemUpdatePolicy()).isNull(); @@ -425,6 +472,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getProfileOwnerKeys()).isEmpty(); assertThat(owners.getSystemUpdatePolicy()).isNotNull(); @@ -444,6 +493,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.hasDeviceOwner()).isFalse(); assertThat(owners.getDeviceOwnerUserId()).isEqualTo(UserHandle.USER_NULL); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getProfileOwnerKeys()).isEmpty(); assertThat(owners.getSystemUpdatePolicy()).isNotNull(); @@ -472,9 +523,16 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getLegacyConfigFile().exists()).isFalse(); assertThat(owners.getDeviceOwnerFile().exists()).isTrue(); + assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); assertThat(owners.getProfileOwnerFile(10).exists()).isTrue(); assertThat(owners.getProfileOwnerFile(11).exists()).isTrue(); + String previousDeviceOwnerPackageName = owners.getDeviceOwnerPackageName(); + owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED); + assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo( + DEVICE_OWNER_TYPE_FINANCED); + // Then clear all information and save. owners.clearDeviceOwner(); owners.clearSystemUpdatePolicy(); @@ -491,5 +549,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getDeviceOwnerFile().exists()).isFalse(); assertThat(owners.getProfileOwnerFile(10).exists()).isFalse(); assertThat(owners.getProfileOwnerFile(11).exists()).isFalse(); + + assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo( + DEVICE_OWNER_TYPE_DEFAULT); } } 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..54825ee2745a 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -64,7 +64,6 @@ public class AutomaticBrightnessControllerTest { @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; @Mock Handler mNoOpHandler; - @Mock DisplayDeviceConfig mDisplayDeviceConfig; @Mock DisplayDevice mDisplayDevice; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; 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..285806b5dcd7 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -88,7 +88,9 @@ public class BrightnessMappingStrategyTest { }; private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; - private static final int[] BACKLIGHT_RANGE = { 1, 255 }; + private static final float[] DISPLAY_LEVELS_RANGE_NITS = { 13.25f, 478.5f }; + private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; + private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f }; private static final float[] EMPTY_FLOAT_ARRAY = new float[0]; private static final int[] EMPTY_INT_ARRAY = new int[0]; @@ -114,25 +116,28 @@ public class BrightnessMappingStrategyTest { }; private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline( new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f }, - new float[] { 0.035f, 0.035f, 0.221f, 0.523f, 0.797f, 0.980f, 1.0f }); + new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f }); @Test public void testSimpleStrategyMappingAtControlPoints() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 0; i < LUX_LEVELS.length; i++) { - final float expectedLevel = - (float) DISPLAY_LEVELS_BACKLIGHT[i] / PowerManager.BRIGHTNESS_ON; + final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1, + PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]); assertEquals(expectedLevel, - simple.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/); + simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/); } } @Test public void testSimpleStrategyMappingBetweenControlPoints() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 1; i < LUX_LEVELS.length; i++) { final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; @@ -146,66 +151,71 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNewConfiguration() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); - final int N = LUX_LEVELS.length; final float[] lux = { 0f, 1f }; final float[] nits = { 0, PowerManager.BRIGHTNESS_ON }; BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .build(); strategy.setBrightnessConfiguration(config); - assertNotEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/); + assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/); } @Test public void testSimpleStrategyIgnoresNullConfiguration() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); strategy.setBrightnessConfiguration(null); final int N = DISPLAY_LEVELS_BACKLIGHT.length; final float expectedBrightness = (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01 /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); } @Test public void testPhysicalStrategyMappingAtControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", physical); for (int i = 0; i < LUX_LEVELS.length; i++) { - final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1]; + final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1], + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0], + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1], + DISPLAY_LEVELS_NITS[i]); assertEquals(expectedLevel, - physical.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/); + physical.getBrightness(LUX_LEVELS[i]), + 0.0001f /*tolerance*/); } } @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", physical); - Spline backlightToBrightness = - Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS); + Spline brightnessToNits = + Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS); for (int i = 1; i < LUX_LEVELS.length; i++) { - final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; - final float backlight = physical.getBrightness(lux) * PowerManager.BRIGHTNESS_ON; - final float nits = backlightToBrightness.interpolate(backlight); - assertTrue("Desired brightness should be between adjacent control points.", + final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f; + final float brightness = physical.getBrightness(lux); + final float nits = brightnessToNits.interpolate(brightness); + assertTrue("Desired brightness should be between adjacent control points: " + nits, nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]); } } @Test public void testPhysicalStrategyUsesNewConfigurations() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); final float[] lux = { 0f, 1f }; final float[] nits = { @@ -216,46 +226,53 @@ public class BrightnessMappingStrategyTest { BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .build(); strategy.setBrightnessConfiguration(config); - assertEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/); + assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/); // Check that null returns us to the default configuration. strategy.setBrightnessConfiguration(null); final int N = DISPLAY_LEVELS_NITS.length; final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1]; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); } @Test public void testPhysicalStrategyRecalculateSplines() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, - BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) { adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f; } // Default is unadjusted - assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), + 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), + 0.0001f /* tolerance */); // When adjustment is turned on, adjustment array is used strategy.recalculateSplines(true, adjustedNits50p); - assertEquals(1.3425f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(239.25f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0] / 2, + strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1] / 2, + strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), 0.0001f /* tolerance */); // When adjustment is turned off, adjustment array is ignored strategy.recalculateSplines(false, adjustedNits50p); - assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), + 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), + 0.0001f /* tolerance */); } @Test public void testDefaultStrategyIsPhysical() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); } @@ -266,15 +283,15 @@ public class BrightnessMappingStrategyTest { int tmp = lux[idx]; lux[idx] = lux[idx+1]; lux[idx+1] = tmp; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. lux[idx] = lux[idx+1]; - res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - strategy = BrightnessMappingStrategy.create(res); + res = createResources(lux, DISPLAY_LEVELS_NITS); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); } @@ -285,13 +302,13 @@ public class BrightnessMappingStrategyTest { // Make sure it's strictly increasing so that the only failure is the differing array // lengths lux[lux.length - 1] = lux[lux.length - 2] + 1; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT); - strategy = BrightnessMappingStrategy.create(res); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // Extra backlight level @@ -299,43 +316,45 @@ public class BrightnessMappingStrategyTest { DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; res = createResources(LUX_LEVELS, backlight); - strategy = BrightnessMappingStrategy.create(res); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // Extra nits level final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1); nits[nits.length - 1] = nits[nits.length - 2] + 1; - res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - strategy = BrightnessMappingStrategy.create(res); + res = createResources(LUX_LEVELS, nits); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); } @Test public void testPhysicalStrategyRequiresNitsMapping() { Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/); - physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, - EMPTY_INT_ARRAY /*backlightRange*/); - physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); } @Test public void testStrategiesAdaptToUserDataPoint() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc)); + ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc)); } private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) { @@ -351,7 +370,7 @@ public class BrightnessMappingStrategyTest { // Then make sure that all control points after the middle lux level are also set to max... for (int i = idx; i < LUX_LEVELS.length; i++) { - assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/); + assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/); } // ...and that all control points before the middle lux level are strictly less than the @@ -369,12 +388,12 @@ public class BrightnessMappingStrategyTest { strategy.clearUserDataPoints(); for (int i = 0; i < LUX_LEVELS.length; i++) { assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]), - 0.01 /*tolerance*/); + 0.0001f /*tolerance*/); } // Now set the middle of the lux range to something just above the minimum. float minBrightness = strategy.getBrightness(LUX_LEVELS[0]); - strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f); + strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f); // Then make sure the curve is still monotonic. prevBrightness = 0f; @@ -389,31 +408,21 @@ public class BrightnessMappingStrategyTest { // be true assuming that there are more than two lux levels in the curve since we picked a // brightness just barely above the minimum for the middle of the curve. minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction. - assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.01 /*tolerance*/); - } - - private static float[] toFloatArray(int[] vals) { - float[] newVals = new float[vals.length]; - for (int i = 0; i < vals.length; i++) { - newVals[i] = (float) vals[i]; - } - return newVals; + assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/); } private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) { return createResources(luxLevels, brightnessLevelsBacklight, - EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/, - EMPTY_INT_ARRAY /*backlightRange*/); + EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/); } - private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits, - float[] nitsRange, int[] backlightRange) { + private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits) { return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - brightnessLevelsNits, nitsRange, backlightRange); + brightnessLevelsNits); } private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, - float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) { + float[] brightnessLevelsNits) { Resources mockResources = mock(Resources.class); // For historical reasons, the lux levels resource implicitly defines the first point as 0, // so we need to chop it off of the array the mock resource object returns. @@ -430,15 +439,6 @@ public class BrightnessMappingStrategyTest { com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mockBrightnessLevelNits); - TypedArray mockNitsRange = createFloatTypedArray(nitsRange); - when(mockResources.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)) - .thenReturn(mockNitsRange); - - when(mockResources.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight)) - .thenReturn(backlightRange); - when(mockResources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum)) .thenReturn(1); @@ -451,6 +451,21 @@ public class BrightnessMappingStrategyTest { return mockResources; } + private DisplayDeviceConfig createDdc() { + return createDdc(DISPLAY_RANGE_NITS); + } + + private DisplayDeviceConfig createDdc(float[] nitsArray) { + return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT); + } + + private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) { + DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); + when(mockDdc.getNits()).thenReturn(nitsArray); + when(mockDdc.getBrightness()).thenReturn(backlightArray); + return mockDdc; + } + private TypedArray createFloatTypedArray(float[] vals) { TypedArray mockArray = mock(TypedArray.class); when(mockArray.length()).thenAnswer(invocation -> { @@ -488,21 +503,22 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Let's start with a validity check: - assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */); + assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */); // OK, let's roll: float gamma = 0.5f; strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma)); - assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.01f /* tolerance */); - // The adjustment should be +0.63 (manual calculation). - assertEquals(+0.63f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */); + // The adjustment should be +0.6308 (manual calculation). + assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test @@ -516,39 +532,39 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Validity check: - assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */); + assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */); // Let's roll: float gamma = 0.25f; final float minGamma = 1.0f / MAXIMUM_GAMMA; strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma)); - assertEquals(MathUtils.pow(y1, minGamma), strategy.getBrightness(x1), - 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), - 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y3, minGamma), strategy.getBrightness(x3), - 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y1, minGamma), + strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), + strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y3, minGamma), + strategy.getBrightness(x3), 0.0001f /* tolerance */); // The adjustment should be +1.0 (maximum adjustment). - assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test public void testGammaCorrectionExtremeChangeAtCenter() { // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we // just make sure the adjustment reflects the change. - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); - assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); + assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); strategy.addUserDataPoint(2500, 1.0f); - assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); strategy.addUserDataPoint(2500, 0.0f); - assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test @@ -562,28 +578,28 @@ public class BrightnessMappingStrategyTest { final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Validity, as per tradition: - assertEquals(y0, strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y4, strategy.getBrightness(x4), 0.01f /* tolerance */); + assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */); // Rollin': float adjustment = 0.3f; float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment); strategy.addUserDataPoint(x0, y0 + adjustment); - assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.01f /* tolerance */); - assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */); + assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4. adjustment = 1.0f - y4; gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment); strategy.addUserDataPoint(x4, 1.0f); - assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(1.0f, strategy.getBrightness(x4), 0.01f /* tolerance */); - assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */); + assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java index 27fce3c37fd9..912da9414165 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java @@ -45,7 +45,7 @@ public final class PersistentSystemFontConfigTest { public void testWriteRead() throws Exception { long expectedModifiedDate = 1234567890; PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; config.updatedFontDirs.add("~~abc"); config.updatedFontDirs.add("~~def"); @@ -65,7 +65,7 @@ public final class PersistentSystemFontConfigTest { PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config(); PersistentSystemFontConfig.loadFromXml(bais, another); - assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate); + assertThat(another.lastModifiedMillis).isEqualTo(expectedModifiedDate); assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def"); assertThat(another.fontFamilies).containsExactly(fontFamily); } @@ -82,7 +82,7 @@ public final class PersistentSystemFontConfigTest { new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); PersistentSystemFontConfig.loadFromXml(bais, config); - assertThat(config.lastModifiedDate).isEqualTo(0); + assertThat(config.lastModifiedMillis).isEqualTo(0); } } 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 7771afc8c7f1..843296e31800 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 @@ -151,7 +151,7 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; writeConfig(config, mConfigFile); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, @@ -507,7 +507,7 @@ public final class UpdatableFontDirTest { File readonlyFile = new File(readonlyDir, "readonly_config.xml"); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; writeConfig(config, readonlyFile); assertThat(readonlyDir.setWritable(false, false)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 7d7af03ecd3d..74bf4f5da70d 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -115,7 +115,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; @@ -386,8 +385,7 @@ public class NetworkPolicyManagerServiceTest { Log.d(TAG, "set mUidObserver to " + mUidObserver); return null; } - }).when(mActivityManager).registerUidObserver(any(), anyInt(), - eq(NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE), any(String.class)); + }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class)); mFutureIntent = newRestrictBackgroundChangedFuture(); mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, diff --git a/services/tests/servicestests/src/com/android/server/pm/OWNERS b/services/tests/servicestests/src/com/android/server/pm/OWNERS index d825dfd7cf00..e15b5f57069c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/OWNERS +++ b/services/tests/servicestests/src/com/android/server/pm/OWNERS @@ -1 +1,3 @@ include /services/core/java/com/android/server/pm/OWNERS + +per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index ff43da6370e8..ee0a16a70265 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -86,6 +86,7 @@ public class DexManagerTests { private TestData mBarUser0DelegateLastClassLoader; private TestData mSystemServerJar; + private TestData mSystemServerJarUpdatedContext; private TestData mSystemServerJarInvalid; private int mUser0; @@ -113,6 +114,8 @@ public class DexManagerTests { mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mSystemServerJarUpdatedContext = new TestData("android", isa, mUser0, + DELEGATE_LAST_CLASS_LOADER_NAME); mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); @@ -522,6 +525,24 @@ public class DexManagerTests { } @Test + public void testSystemServerOverwritesContext() { + // Record bar secondaries with the default PathClassLoader. + List<String> secondaries = mSystemServerJar.getSecondaryDexPaths(); + + notifyDexLoad(mSystemServerJar, secondaries, mUser0); + PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); + assertSecondaryUse(mSystemServerJar, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + + // Record bar secondaries again with a different class loader. This will change the context. + notifyDexLoad(mSystemServerJarUpdatedContext, secondaries, mUser0); + + pui = getPackageUseInfo(mSystemServerJar); + // We expect that all the contexts to be updated according to the last notify. + assertSecondaryUse(mSystemServerJarUpdatedContext, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0); + } + + @Test public void testNotifyUnsupportedClassLoaderDoesNotChangeExisting() { List<String> secondaries = mBarUser0.getSecondaryDexPaths(); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/OWNERS b/services/tests/servicestests/src/com/android/server/pm/dex/OWNERS new file mode 100644 index 000000000000..66ef75d6c823 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pm/dex/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index adf4551e79a8..3450710f60a0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -451,7 +451,7 @@ public class PackageDexUsageTests { "PCL[new_context.dex]"); assertTrue(record(fooSecondary1User0NewContext)); - // Not check that the context was switch to variable. + // Now check that the context was switch to variable. TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext( PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); @@ -461,6 +461,22 @@ public class PackageDexUsageTests { } @Test + public void testRecordClassLoaderContextOverwritten() { + // Record a secondary dex file. + assertTrue(record(mFooSecondary1User0)); + // Now update its context. + TestData fooSecondary1User0NewContext = mFooSecondary1User0.updateClassLoaderContext( + "PCL[new_context.dex]", true); + assertTrue(record(fooSecondary1User0NewContext)); + + // Now check that the context was overwritten. + TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext( + "PCL[new_context.dex]", true); + + assertPackageDexUsage(null, expectedContext); + } + + @Test public void testDexUsageClassLoaderContext() { final boolean isUsedByOtherApps = false; final int userId = 0; @@ -642,8 +658,9 @@ public class PackageDexUsageTests { private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, - testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); + testData.mOwnerUserId, testData.mLoaderIsa, + testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext, + testData.mOverwriteCLC); } private boolean record(PackageDexUsage packageDexUsage, TestData testData, Set<String> users) { @@ -651,7 +668,8 @@ public class PackageDexUsageTests { for (String user : users) { result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, testData.mOwnerUserId, testData.mLoaderIsa, - testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); + testData.mPrimaryOrSplit, user, testData.mClassLoaderContext, + testData.mOverwriteCLC); } return result; } @@ -682,15 +700,16 @@ public class PackageDexUsageTests { private final boolean mPrimaryOrSplit; private final String mUsedBy; private final String mClassLoaderContext; + private final boolean mOverwriteCLC; private TestData(String packageName, String dexFile, int ownerUserId, String loaderIsa, boolean primaryOrSplit, String usedBy) { this(packageName, dexFile, ownerUserId, loaderIsa, primaryOrSplit, - usedBy, "PCL[" + dexFile + "]"); + usedBy, "PCL[" + dexFile + "]", false); } private TestData(String packageName, String dexFile, int ownerUserId, String loaderIsa, boolean primaryOrSplit, String usedBy, - String classLoaderContext) { + String classLoaderContext, boolean overwriteCLC) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; @@ -698,16 +717,21 @@ public class PackageDexUsageTests { mPrimaryOrSplit = primaryOrSplit; mUsedBy = usedBy; mClassLoaderContext = classLoaderContext; + mOverwriteCLC = overwriteCLC; } private TestData updateClassLoaderContext(String newContext) { + return updateClassLoaderContext(newContext, mOverwriteCLC); + } + + private TestData updateClassLoaderContext(String newContext, boolean overwriteCLC) { return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, - mPrimaryOrSplit, mUsedBy, newContext); + mPrimaryOrSplit, mUsedBy, newContext, overwriteCLC); } private TestData updateUsedBy(String newUsedBy) { return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, - mPrimaryOrSplit, newUsedBy, mClassLoaderContext); + mPrimaryOrSplit, newUsedBy, mClassLoaderContext, mOverwriteCLC); } private boolean isUsedByOtherApps() { diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index ddbe81c81d6d..26b34fdd4e04 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -318,7 +318,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyMeasurement.length == ENERGY_METER_COUNT); for (int i = 0; i < pssProto.energyMeasurement.length; i++) { assertTrue(pssProto.energyMeasurement[i].id == i); - assertTrue(pssProto.energyMeasurement[i].timestampMs == i); + assertTrue(pssProto.energyMeasurement[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyMeasurement[i].durationMs == i); assertTrue(pssProto.energyMeasurement[i].energyUws == i); } @@ -359,7 +360,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyConsumerResult.length == ENERGY_CONSUMER_COUNT); for (int i = 0; i < pssProto.energyConsumerResult.length; i++) { assertTrue(pssProto.energyConsumerResult[i].id == i); - assertTrue(pssProto.energyConsumerResult[i].timestampMs == i); + assertTrue(pssProto.energyConsumerResult[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyConsumerResult[i].energyUws == i); assertTrue(pssProto.energyConsumerResult[i].attribution.length == ENERGY_CONSUMER_ATTRIBUTION_COUNT); @@ -420,7 +422,8 @@ public class PowerStatsServiceTest { assertTrue(stateResidency.id == j); assertTrue(stateResidency.totalTimeInStateMs == j); assertTrue(stateResidency.totalStateEntryCount == j); - assertTrue(stateResidency.lastEntryTimestampMs == j); + assertTrue(stateResidency.lastEntryTimestampMs == + j + mPowerStatsLogger.getStartWallTime()); } } } diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS index 816bc6bba639..33385afbdfd6 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS @@ -1 +1 @@ -include /core/java/android/media/soundtrigger/OWNERS +include /media/aidl/android/media/soundtrigger_middleware/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 682a80c9b297..5d2755221288 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -46,8 +46,8 @@ public class ConfigurationInternalTest { public void test_unrestricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -108,8 +108,8 @@ public class ConfigurationInternalTest { public void test_restricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -170,8 +170,8 @@ public class ConfigurationInternalTest { public void test_autoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -232,8 +232,8 @@ public class ConfigurationInternalTest { public void test_geoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index d2452eaee657..14e0bbd6fa42 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -364,8 +364,8 @@ public class TimeZoneDetectorServiceTest { // the tests. final boolean geoDetectionEnabled = autoDetectionEnabled; return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(autoDetectionEnabled) .setLocationEnabled(geoDetectionEnabled) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index c8dba5f40882..f1f8b2f5e81a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -90,8 +90,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -100,8 +100,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -110,8 +110,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -120,8 +120,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -130,8 +130,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -139,8 +139,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -149,8 +149,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index d319488ba73b..8280cdcb18c4 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -44,8 +44,8 @@ final class TestSupport { @UserIdInt int userId, boolean geoDetectionEnabled) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(geoDetectionEnabled) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a64050996d42..cebdbbef2329 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3671,8 +3671,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true); - mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(), - r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, + mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getUserId(), + r.getKey(), NotificationStats.DISMISSAL_AOD, NotificationStats.DISMISS_SENTIMENT_POSITIVE, nv); waitForIdle(); @@ -3694,8 +3694,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true); - mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(), - r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, + mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getUserId(), + r.getKey(), NotificationStats.DISMISSAL_AOD, NotificationStats.DISMISS_SENTIMENT_NEGATIVE, nv); waitForIdle(); @@ -6693,8 +6693,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, - nrSummary.getSbn().getTag(), - nrSummary.getSbn().getId(), nrSummary.getUserId(), nrSummary.getKey(), + nrSummary.getUserId(), nrSummary.getKey(), NotificationStats.DISMISSAL_SHADE, NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv); waitForIdle(); diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java new file mode 100644 index 000000000000..3025a95be98c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -0,0 +1,152 @@ +/* + * 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.policy; + +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.ACTION_UP; +import static android.view.KeyEvent.KEYCODE_POWER; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.content.Context; +import android.os.SystemClock; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test class for {@link SingleKeyGestureDetector}. + * + * Build/Install/Run: + * atest WmTests:SingleKeyGestureTests + */ +public class SingleKeyGestureTests { + private SingleKeyGestureDetector mDetector; + + private int mMaxMultiPressPowerCount = 2; + + private CountDownLatch mShortPressed = new CountDownLatch(1); + private CountDownLatch mLongPressed = new CountDownLatch(1); + private CountDownLatch mVeryLongPressed = new CountDownLatch(1); + private CountDownLatch mMultiPressed = new CountDownLatch(1); + + private final Instrumentation mInstrumentation = getInstrumentation(); + private final Context mContext = mInstrumentation.getTargetContext(); + private long mWaitTimeout; + private long mLongPressTime; + private long mVeryLongPressTime; + + @Before + public void setUp() { + mDetector = new SingleKeyGestureDetector(mContext); + initSingleKeyGestureRules(); + mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50; + mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50; + mVeryLongPressTime = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout) + 50; + } + + private void initSingleKeyGestureRules() { + mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER, + KEY_LONGPRESS | KEY_VERYLONGPRESS) { + @Override + int getMaxMultiPressCount() { + return mMaxMultiPressPowerCount; + } + @Override + public void onPress(long downTime) { + mShortPressed.countDown(); + } + + @Override + void onLongPress(long downTime) { + mLongPressed.countDown(); + } + + @Override + void onVeryLongPress(long downTime) { + mVeryLongPressed.countDown(); + } + + @Override + void onMultiPress(long downTime, int count) { + mMultiPressed.countDown(); + assertEquals(mMaxMultiPressPowerCount, count); + } + }); + } + + private void pressKey(long eventTime, int keyCode, long pressTime) { + final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN, + keyCode, 0 /* repeat */, 0 /* metaState */); + mDetector.interceptKey(keyDown); + + // keep press down. + try { + Thread.sleep(pressTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + eventTime += pressTime; + final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP, + keyCode, 0 /* repeat */, 0 /* metaState */); + + mDetector.interceptKey(keyUp); + } + + @Test + public void testShortPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testLongPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, mLongPressTime); + assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testVeryLongPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, mVeryLongPressTime); + assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testMultiPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } +} 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 aa1110cd55a7..990927b704fa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -555,9 +555,10 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation( isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); - // Asserts it has orientation derived from bounds. - assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT, + // Asserts it has orientation derived requested orientation (fixed orientation letterbox). + assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation); + assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); } @Test @@ -2288,7 +2289,7 @@ public class ActivityRecordTests extends WindowTestsBase { IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); reset(task); activity.reportDescendantOrientationChangeIfNeeded(); - verify(task).onConfigurationChanged(any(Configuration.class)); + verify(task, atLeast(1)).onConfigurationChanged(any(Configuration.class)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index d13e4dcaf9fd..7df17fd4b3c6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -38,6 +38,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; +import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID; import static com.google.common.truth.Truth.assertThat; @@ -595,6 +596,17 @@ public class DisplayAreaPolicyBuilderTest { assertThat(token.isDescendantOf(mRoot)).isTrue(); assertThat(token.isDescendantOf(mGroupRoot1)).isFalse(); assertThat(token.isDescendantOf(mGroupRoot2)).isFalse(); + + // When the window has options for target root id, attach it to the target root. + final Bundle options = new Bundle(); + options.putInt(KEY_ROOT_DISPLAY_AREA_ID, mGroupRoot2.mFeatureId); + final WindowToken token2 = new WindowToken(mWms, mock(IBinder.class), + TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent, + true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */, + false /* fromClientToken */, options); + policy.addWindow(token2); + + assertThat(token2.isDescendantOf(mGroupRoot2)).isTrue(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 58169bb1e073..137cf6523caf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -32,6 +32,8 @@ import static android.view.DisplayCutout.fromBoundingRect; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; @@ -121,6 +123,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.WmDisplayCutout; import org.junit.Test; @@ -955,16 +958,14 @@ public class DisplayContentTests extends WindowTestsBase { IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); final int newOrientation = getRotatedOrientation(dc); - final Task stack = new TaskBuilder(mSupervisor) + final Task task = new TaskBuilder(mSupervisor) .setDisplay(dc).setCreateActivity(true).build(); - final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity(); + final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity(); + dc.setFocusedApp(activity); activity.setRequestedOrientation(newOrientation); - final int expectedOrientation = newOrientation == SCREEN_ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_PORTRAIT - : Configuration.ORIENTATION_LANDSCAPE; - assertEquals(expectedOrientation, dc.getConfiguration().orientation); + assertTrue("The display should be rotated.", dc.getRotation() % 2 == 1); } @Test @@ -972,17 +973,42 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); + dc.getDisplayRotation().setUserRotation( + WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_180); final int newOrientation = getRotatedOrientation(dc); - final Task stack = new TaskBuilder(mSupervisor) + final Task task = new TaskBuilder(mSupervisor) .setDisplay(dc).setCreateActivity(true).build(); - final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity(); + final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity(); + dc.setFocusedApp(activity); activity.setRequestedOrientation(newOrientation); verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity), anyBoolean(), same(null)); - assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation()); + assertEquals(ROTATION_180, dc.getRotation()); + } + + @Test + public void testFixedToUserRotationChanged() { + final DisplayContent dc = createNewDisplay(); + dc.getDisplayRotation().setFixedToUserRotation( + IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); + dc.getDisplayRotation().setUserRotation( + WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0); + final int newOrientation = getRotatedOrientation(dc); + + final Task task = new TaskBuilder(mSupervisor) + .setDisplay(dc).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity(); + dc.setFocusedApp(activity); + + activity.setRequestedOrientation(newOrientation); + + dc.getDisplayRotation().setFixedToUserRotation( + IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); + + assertTrue("The display should be rotated.", dc.getRotation() % 2 == 1); } @Test @@ -1418,7 +1444,7 @@ public class DisplayContentTests extends WindowTestsBase { // Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task. pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); displayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); - assertFalse(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging()); + assertFalse(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging()); assertEquals(pinnedConfigOrientation, displayConfig.orientation); clearInvocations(mWm); @@ -1429,7 +1455,7 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(displayContent.hasTopFixedRotationLaunchingApp()); verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt()); assertEquals(homeConfigOrientation, displayConfig.orientation); - assertTrue(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging()); + assertTrue(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging()); } @Test @@ -1820,6 +1846,37 @@ public class DisplayContentTests extends WindowTestsBase { verify(t).show(mDisplayContent.mImeScreenshot); } + @Test + public void testRotateBounds_keepSamePhysicalPosition() { + final DisplayContent dc = + new TestDisplayContent.Builder(mAtm, 1000, 2000).build(); + final Rect initBounds = new Rect(0, 0, 700, 1500); + final Rect rotateBounds = new Rect(initBounds); + + // Rotate from 0 to 0 + dc.rotateBounds(ROTATION_0, ROTATION_0, rotateBounds); + + assertEquals(new Rect(0, 0, 700, 1500), rotateBounds); + + // Rotate from 0 to 90 + rotateBounds.set(initBounds); + dc.rotateBounds(ROTATION_0, ROTATION_90, rotateBounds); + + assertEquals(new Rect(0, 300, 1500, 1000), rotateBounds); + + // Rotate from 0 to 180 + rotateBounds.set(initBounds); + dc.rotateBounds(ROTATION_0, ROTATION_180, rotateBounds); + + assertEquals(new Rect(300, 500, 1000, 2000), rotateBounds); + + // Rotate from 0 to 270 + rotateBounds.set(initBounds); + dc.rotateBounds(ROTATION_0, ROTATION_270, rotateBounds); + + assertEquals(new Rect(500, 0, 2000, 700), rotateBounds); + } + private boolean isOptionsPanelAtRight(int displayId) { return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT; } diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index f91c9d0e9853..e9c356d6c6c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -171,7 +171,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityBounds = new Rect(mFirstActivity.getBounds()); // DAG is portrait (860x1200), so Task and Activity fill DAG. - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); assertThat(taskBounds).isEqualTo(dagBounds); assertThat(activityBounds).isEqualTo(taskBounds); @@ -194,8 +194,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityConfigBounds = new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds()); - // DAG is landscape (1200x860), Task fills parent - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -211,7 +211,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testLaunchLandscapeApp_taskIsLetterboxInDisplayAreaGroup() { + public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -221,17 +221,18 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect taskBounds = new Rect(mFirstTask.getBounds()); final Rect activityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is portrait (860x1200), so Task is letterbox (860x[860x860/1200=616]) - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation + // (860x[860x860/1200=616]). Task fills DAG. + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); - assertThat(taskBounds.width()).isEqualTo(dagBounds.width()); - assertThat(taskBounds.height()) + assertThat(taskBounds).isEqualTo(dagBounds); + assertThat(activityBounds.width()).isEqualTo(dagBounds.width()); + assertThat(activityBounds.height()) .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height()); - assertThat(activityBounds).isEqualTo(taskBounds); } @Test - public void testLaunchLandscapeApp_taskLetterboxBecomesActivityLetterboxAfterRotation() { + public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -245,9 +246,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect newTaskBounds = new Rect(mFirstTask.getBounds()); final Rect newActivityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is landscape (1200x860), Task fills parent - // Task letterbox size - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -311,7 +311,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testResizableFixedOrientationApp_taskLevelLetterboxing() { + public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -324,7 +324,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); // Launch portrait on second DAG @@ -336,13 +336,13 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); - assertThat(mSecondTask.isTaskLetterboxed()).isFalse(); + assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mSecondActivity.inSizeCompatMode()).isFalse(); // First activity is letterboxed in portrait as requested. assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); } 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 e843dd71381f..b73c66407874 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -43,7 +44,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.same; @@ -126,6 +126,7 @@ public class SizeCompatTests extends WindowTestsBase { // Put app window into freeform and then make it a compat app. final Rect bounds = new Rect(100, 100, 400, 600); mTask.setBounds(bounds); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertEquals(bounds, mActivity.getBounds()); @@ -194,12 +195,7 @@ public class SizeCompatTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 2000) .setDensityDpi(200).build(); - mActivity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setResizeMode(RESIZE_MODE_UNRESIZEABLE) - .setMaxAspectRatio(1.5f) - .build(); - mActivity.mVisibleRequested = true; + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; @@ -566,18 +562,18 @@ public class SizeCompatTests extends WindowTestsBase { .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) .build(); - assertTrue(activity.shouldUseSizeCompatMode()); + assertTrue(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because it is on a resizable task // in multi-window mode. mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -597,7 +593,7 @@ public class SizeCompatTests extends WindowTestsBase { SizeCompatTests.class.getName())) .setUid(android.os.Process.myUid()) .build(); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -653,7 +649,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -662,7 +658,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - final Rect taskBounds = new Rect(mTask.getBounds()); final Rect activityBounds = new Rect(mActivity.getBounds()); // Display shouldn't be rotated. @@ -670,19 +665,19 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.getLastOrientation()); assertTrue(displayBounds.width() > displayBounds.height()); - // App should launch in task level letterboxing. - assertTrue(mTask.isTaskLetterboxed()); + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(taskBounds, activityBounds); - // Task bounds should be 700x1400 with the ratio as the display. - assertEquals(displayBounds.height(), taskBounds.height()); + // Activity bounds should be 700x1400 with the ratio as the display. + assertEquals(displayBounds.height(), activityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + activityBounds.width()); } @Test - public void testDisplayIgnoreOrientationRequest_taskLetterboxBecameSizeCompatAfterRotate() { + public void + testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -700,7 +695,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() < displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); @@ -719,7 +714,7 @@ public class SizeCompatTests extends WindowTestsBase { Rect activityBounds = new Rect(mActivity.getBounds()); // App should launch in fullscreen. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(displayBounds, activityBounds); @@ -731,7 +726,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() > displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // App bounds should be 700x1400 with the ratio as the display. @@ -741,7 +736,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); final DisplayContent display = mActivity.mDisplayContent; @@ -750,7 +745,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app. @@ -765,19 +760,19 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task and app bounds should be 700x1400 with the ratio as the display. - assertTrue(mTask.isTaskLetterboxed()); + // Task and display bounds should be equal while activity should be letterboxed and + // has 700x1400 bounds with the ratio as the display. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); - assertEquals(displayBounds.height(), taskBounds.height()); + assertEquals(taskBounds, displayBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + newActivityBounds.width()); } @Test @@ -790,7 +785,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app with max aspect ratio as 1.3. @@ -806,21 +801,20 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. - assertTrue(mTask.isTaskLetterboxed()); - assertEquals(displayBounds.height(), taskBounds.height()); - assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio), - taskBounds.width()); + // Task bounds should fill parent bounds. + assertEquals(displayBounds, taskBounds); - // App bounds should be fullscreen in Task bounds. + // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); + assertEquals((long) Math.rint(newActivityBounds.height() / newActivity.info.maxAspectRatio), + newActivityBounds.width()); } @Test @@ -834,26 +828,23 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); clearInvocations(mActivity); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); // Rotate display to portrait. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); final Rect activityBounds = new Rect(mActivity.getBounds()); mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); // App still in size compat, and the bounds don't change. verify(mActivity, never()).clearSizeCompatMode(); - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); assertEquals(activityBounds, mActivity.getBounds()); } @@ -867,22 +858,22 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -898,22 +889,22 @@ public class SizeCompatTests extends WindowTestsBase { // Landscape fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -972,21 +963,21 @@ public class SizeCompatTests extends WindowTestsBase { addWindowToActivity(mActivity); mActivity.mRootWindowContainer.performSurfacePlacement(); - // Split screen is also in portrait [1000,1400], so Task should be in letterbox, and - // activity fills task. - assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation); + // Split screen is also in portrait [1000,1400], so activty should be in fixed orientation + // letterbox. + assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation); assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation); assertFitted(); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); - // Letterbox should fill the gap between the split screen and the letterboxed task. + // Letterbox should fill the gap between the split screen and the letterboxed activity. final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds()); - final Rect letterboxedTaskBounds = new Rect(mTask.getBounds()); - assertTrue(primarySplitBounds.contains(letterboxedTaskBounds)); - assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left, - letterboxedTaskBounds.top - primarySplitBounds.top, - primarySplitBounds.right - letterboxedTaskBounds.right, - primarySplitBounds.bottom - letterboxedTaskBounds.bottom), + final Rect letterboxedBounds = new Rect(mActivity.getBounds()); + assertTrue(primarySplitBounds.contains(letterboxedBounds)); + assertEquals(new Rect(letterboxedBounds.left - primarySplitBounds.left, + letterboxedBounds.top - primarySplitBounds.top, + primarySplitBounds.right - letterboxedBounds.right, + primarySplitBounds.bottom - letterboxedBounds.bottom), mActivity.getLetterboxInsets()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 0eb8c8d2e58a..d853b930af11 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -266,8 +266,9 @@ public class TaskRecordTests extends WindowTestsBase { root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); - assertEquals(fullScreenBounds.height(), task.getBounds().height()); + // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. + assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); + assertEquals(fullScreenBounds, task.getBounds()); // Top activity gets used final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack) @@ -286,8 +287,11 @@ public class TaskRecordTests extends WindowTestsBase { // Fix the display orientation to portrait which is 90 degrees for the test display. dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + // Fixed orientation request should be resolved on activity level. Task fills display + // bounds. + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // in FREEFORM, no constraint final Rect freeformBounds = new Rect(display.getBounds()); @@ -297,10 +301,11 @@ public class TaskRecordTests extends WindowTestsBase { task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); - // FULLSCREEN letterboxes bounds + // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // FREEFORM restores bounds as before stack.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -327,9 +332,10 @@ public class TaskRecordTests extends WindowTestsBase { assertEquals(fullScreenBounds, task.getBounds()); - // Setting app to fixed portrait fits within parent + // Setting app to fixed portrait fits within parent on activity level. Task fills parent. root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); + assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); + assertEquals(task.getBounds(), fullScreenBounds); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); } @@ -424,7 +430,8 @@ public class TaskRecordTests extends WindowTestsBase { // to the input bounds. final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); final ActivityRecord.CompatDisplayInsets compatIntsets = - new ActivityRecord.CompatDisplayInsets(display, activity); + new ActivityRecord.CompatDisplayInsets( + display, activity, /* fixedOrientationBounds= */ null); task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1c0f640e1a9c..c3eb5c49cea0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -257,24 +257,4 @@ public class TaskTests extends WindowTestsBase { task.resolveOverrideConfiguration(parentConfig); assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); } - - @Test - public void testCleanUpActivityReferences_clearLastTaskBoundsComputeActivity() { - final Task rootTask = createTaskStackOnDisplay(mDisplayContent); - final Task leafTask = createTaskInStack(rootTask, 0 /* userId */); - final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask); - final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask); - activity1.finishing = false; - leafTask.resolveOverrideConfiguration(rootTask.getConfiguration()); - - assertEquals(activity1, leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity2); - - assertNotNull(leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity1); - - assertNull(leafTask.getLastTaskBoundsComputeActivity()); - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 896969548af3..51aec65f7285 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -251,7 +251,7 @@ public class WindowStateTests extends WindowTestsBase { // Simulate the window is in split screen primary stack and the current state is // minimized and home stack is resizable, so that we should ignore input for the stack. - final DockedStackDividerController controller = + final DockedTaskDividerController controller = mDisplayContent.getDockedDividerController(); final Task stack = createTaskStackOnDisplay(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDisplayContent); diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp index bacc932f760f..391ab8946c2c 100644 --- a/services/texttospeech/Android.bp +++ b/services/texttospeech/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.texttospeech-sources", srcs: ["java/**/*.java"], @@ -10,4 +19,4 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.texttospeech-sources"], libs: ["services.core"], -}
\ No newline at end of file +} diff --git a/services/translation/Android.bp b/services/translation/Android.bp index 804a6177e94e..f257f1b7339a 100644 --- a/services/translation/Android.bp +++ b/services/translation/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "services.translation-sources", srcs: ["java/**/*.java"], @@ -10,4 +19,4 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.translation-sources"], libs: ["services.core"], -}
\ No newline at end of file +} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8628f89a49c1..dce63ebb0889 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -76,6 +76,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVoiceActionCheckCallback; @@ -643,6 +644,10 @@ public class VoiceInteractionManagerService extends SystemService { } ComponentName findAvailRecognizer(String prefPackage, int userHandle) { + if (prefPackage == null) { + prefPackage = getDefaultRecognizer(); + } + List<ResolveInfo> available = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(RecognitionService.SERVICE_INTERFACE), @@ -670,6 +675,12 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + public String getDefaultRecognizer() { + String recognizer = mContext.getString(R.string.config_systemSpeechRecognizer); + return TextUtils.isEmpty(recognizer) ? null : recognizer; + } + ComponentName getCurRecognizer(int userHandle) { String curRecognizer = Settings.Secure.getStringForUser( mContext.getContentResolver(), diff --git a/telecomm/java/android/telecom/Connection.aidl b/telecomm/java/android/telecom/Connection.aidl new file mode 100644 index 000000000000..5b40036e46f4 --- /dev/null +++ b/telecomm/java/android/telecom/Connection.aidl @@ -0,0 +1,22 @@ +/* + * 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.telecom; + +/** + * {@hide} + */ +parcelable Connection.CallFilteringCompletionInfo; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 942a54eb98ba..7c6253ce933a 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -29,6 +29,7 @@ import android.annotation.SystemApi; import android.app.Notification; import android.bluetooth.BluetoothDevice; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Intent; import android.hardware.camera2.CameraManager; import android.net.Uri; @@ -38,7 +39,9 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.telephony.ims.ImsStreamMediaProfile; @@ -3379,6 +3382,121 @@ public abstract class Connection extends Conferenceable { public void handleRttUpgradeResponse(@Nullable RttTextStream rttTextStream) {} /** + * Information provided to a {@link Connection} upon completion of call filtering in Telecom. + * + * @hide + */ + @SystemApi + public static final class CallFilteringCompletionInfo implements Parcelable { + private final boolean mIsBlocked; + private final boolean mIsInContacts; + private final CallScreeningService.CallResponse mCallResponse; + private final ComponentName mCallScreeningComponent; + + /** + * Constructor for {@link CallFilteringCompletionInfo} + * + * @param isBlocked Whether any part of the call filtering process indicated that this call + * should be blocked. + * @param isInContacts Whether the caller is in the user's contacts. + * @param callResponse The instance of {@link CallScreeningService.CallResponse} provided + * by the {@link CallScreeningService} that processed this call, or + * {@code null} if no call screening service ran. + * @param callScreeningComponent The component of the {@link CallScreeningService} + * that processed this call, or {@link null} if no + * call screening service ran. + */ + public CallFilteringCompletionInfo(boolean isBlocked, boolean isInContacts, + @Nullable CallScreeningService.CallResponse callResponse, + @Nullable ComponentName callScreeningComponent) { + mIsBlocked = isBlocked; + mIsInContacts = isInContacts; + mCallResponse = callResponse; + mCallScreeningComponent = callScreeningComponent; + } + + /** @hide */ + protected CallFilteringCompletionInfo(Parcel in) { + mIsBlocked = in.readByte() != 0; + mIsInContacts = in.readByte() != 0; + CallScreeningService.ParcelableCallResponse response + = in.readParcelable(CallScreeningService.class.getClassLoader()); + mCallResponse = response == null ? null : response.toCallResponse(); + mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader()); + } + + @NonNull + public static final Creator<CallFilteringCompletionInfo> CREATOR = + new Creator<CallFilteringCompletionInfo>() { + @Override + public CallFilteringCompletionInfo createFromParcel(Parcel in) { + return new CallFilteringCompletionInfo(in); + } + + @Override + public CallFilteringCompletionInfo[] newArray(int size) { + return new CallFilteringCompletionInfo[size]; + } + }; + + /** + * @return Whether any part of the call filtering process indicated that this call should be + * blocked. + */ + public boolean isBlocked() { + return mIsBlocked; + } + + /** + * @return Whether the caller is in the user's contacts. + */ + public boolean isInContacts() { + return mIsInContacts; + } + + /** + * @return The instance of {@link CallScreeningService.CallResponse} provided + * by the {@link CallScreeningService} that processed this + * call, or {@code null} if no call screening service ran. + */ + public @Nullable CallScreeningService.CallResponse getCallResponse() { + return mCallResponse; + } + + /** + * @return The component of the {@link CallScreeningService} + * that processed this call, or {@code null} if no call screening service ran. + */ + public @Nullable ComponentName getCallScreeningComponent() { + return mCallScreeningComponent; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "CallFilteringCompletionInfo{" + + "mIsBlocked=" + mIsBlocked + + ", mIsInContacts=" + mIsInContacts + + ", mCallResponse=" + mCallResponse + + ", mCallScreeningPackageName='" + mCallScreeningComponent + '\'' + + '}'; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mIsBlocked ? 1 : 0)); + dest.writeByte((byte) (mIsInContacts ? 1 : 0)); + dest.writeParcelable(mCallResponse == null ? null : mCallResponse.toParcelable(), 0); + dest.writeParcelable(mCallScreeningComponent, 0); + } + } + + /** * Indicates that call filtering in Telecom is complete * * This method is called for a connection created via @@ -3386,24 +3504,13 @@ public abstract class Connection extends Conferenceable { * Telecom, including checking the blocked number db, per-contact settings, and custom call * filtering apps. * - * @param isBlocked {@code true} if the call was blocked, {@code false} otherwise. If this is - * {@code true}, {@link #onDisconnect()} will be called soon after - * this is called. - * @param isInContacts Indicates whether the caller is in the user's contacts list. - * @param callScreeningResponse The response that was returned from the - * {@link CallScreeningService} that handled this call. If no - * response was received from a call screening service, - * this will be {@code null}. - * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the - * system dialer. If {@code callScreeningResponse} is - * {@code null}, this will be {@code false}. + * @param callFilteringCompletionInfo Info provided by Telecom on the results of call filtering. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, - @Nullable CallScreeningService.CallResponse callScreeningResponse, - boolean isResponseFromSystemDialer) { } + public void onCallFilteringCompleted( + @NonNull CallFilteringCompletionInfo callFilteringCompletionInfo) { } static String toLogSafePhoneNumber(String number) { // For unknown number, log empty string. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 966ece3a3ba2..c189b19c71af 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -758,19 +758,15 @@ public abstract class ConnectionService extends Service { } @Override - public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, - CallScreeningService.ParcelableCallResponse callScreeningResponse, - boolean isResponseFromSystemDialer, + public void onCallFilteringCompleted(String callId, + Connection.CallFilteringCompletionInfo completionInfo, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; - args.arg2 = isBlocked; - args.arg3 = isInContacts; - args.arg4 = callScreeningResponse; - args.arg5 = isResponseFromSystemDialer; - args.arg6 = Log.createSubsession(); + args.arg2 = completionInfo; + args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget(); } finally { Log.endSession(); @@ -1441,16 +1437,12 @@ public abstract class ConnectionService extends Service { case MSG_ON_CALL_FILTERING_COMPLETED: { SomeArgs args = (SomeArgs) msg.obj; try { - Log.continueSession((Session) args.arg6, + Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED); String callId = (String) args.arg1; - boolean isBlocked = (boolean) args.arg2; - boolean isInContacts = (boolean) args.arg3; - CallScreeningService.ParcelableCallResponse callScreeningResponse = - (CallScreeningService.ParcelableCallResponse) args.arg4; - boolean isResponseFromSystemDialer = (boolean) args.arg5; - onCallFilteringCompleted(callId, isBlocked, isInContacts, - callScreeningResponse, isResponseFromSystemDialer); + Connection.CallFilteringCompletionInfo completionInfo = + (Connection.CallFilteringCompletionInfo) args.arg2; + onCallFilteringCompleted(callId, completionInfo); } finally { args.recycle(); Log.endSession(); @@ -2466,16 +2458,12 @@ public abstract class ConnectionService extends Service { } } - private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, - CallScreeningService.ParcelableCallResponse callScreeningResponse, - boolean isResponseFromSystemDialer) { - Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId, - isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer); + private void onCallFilteringCompleted(String callId, Connection.CallFilteringCompletionInfo + callFilteringCompletionInfo) { + Log.i(this, "onCallFilteringCompleted(%s, %s)", callId, callFilteringCompletionInfo); Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted"); if (connection != null) { - connection.onCallFilteringCompleted(isBlocked, isInContacts, - callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(), - isResponseFromSystemDialer); + connection.onCallFilteringCompleted(callFilteringCompletionInfo); } } diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 5cf8de8f9078..f20ee7e56d05 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -138,6 +138,24 @@ import java.util.List; * } * } * } + * + * </pre> + * <p id="companionInCallService"> + * <h3>Access to InCallService for Wearable Devices</h3> + * <ol> + * If your app is a third-party companion app and wants to access InCallService APIs, what your + * app could do are: + * <p> + * <ol> + * <li> Declare MANAGE_ONGOING_CALLS permission in your manifest + * <li> Associate with a physical wearable device via the + * {@link android.companion.CompanionDeviceManager} API as a companion app. See: + * https://developer.android.com/guide/topics/connectivity/companion-device-pairing + * <li> Implement this InCallService with BIND_INCALL_SERVICE permission + * </ol> + * </ol> + * <p> + * * </pre> * <p id="incomingCallNotification"> * <h3>Showing the Incoming Call Notification</h3> diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index 6c6097ac71e5..7a6fddb6f029 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -1202,27 +1202,18 @@ public final class RemoteConnection { /** * Notifies this {@link RemoteConnection} that call filtering has completed, as well as * the results of a contacts lookup for the remote party. - * @param isBlocked Whether call filtering indicates that the call should be blocked - * @param isInContacts Whether the remote party is in the user's contacts - * @param callScreeningResponse The response that was returned from the - * {@link CallScreeningService} that handled this call. If no - * response was received from a call screening service, - * this will be {@code null}. - * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the - * system dialer. If {@code callScreeningResponse} is - * {@code null}, this will be {@code false}. + * + * @param completionInfo Info provided by Telecom on the results of call filtering. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, - @Nullable CallScreeningService.CallResponse callScreeningResponse, - boolean isResponseFromSystemDialer) { + public void onCallFilteringCompleted( + @NonNull Connection.CallFilteringCompletionInfo completionInfo) { Log.startSession("RC.oCFC", getActiveOwnerInfo()); try { if (mConnected) { - mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts, - callScreeningResponse.toParcelable(), isResponseFromSystemDialer, + mConnectionService.onCallFilteringCompleted(mConnectionId, completionInfo, null /*Session.Info*/); } } catch (RemoteException ignored) { diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 472d63946ebc..17749e8b0a8f 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1702,22 +1702,22 @@ public class TelecomManager { } /** - * Returns whether the caller has {@link InCallService} access for companion apps. - * - * A companion app is an app associated with a physical wearable device via the - * {@link android.companion.CompanionDeviceManager} API. + * Returns whether the caller has {@link android.Manifest.permission#MANAGE_ONGOING_CALLS} + * permission. The permission can be obtained by associating with a physical wearable device + * via the {@link android.companion.CompanionDeviceManager} API as a companion app. If the + * caller app has the permission, it has {@link InCallService} access to manage ongoing calls. * * @return {@code true} if the caller has {@link InCallService} access for * companion app; {@code false} otherwise. */ - public boolean hasCompanionInCallServiceAccess() { + public boolean hasManageOngoingCallsPermission() { ITelecomService service = getTelecomService(); if (service != null) { try { - return service.hasCompanionInCallServiceAccess( + return service.hasManageOngoingCallsPermission( mContext.getOpPackageName()); } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling hasCompanionInCallServiceAccess().", e); + Log.e(TAG, "RemoteException calling hasManageOngoingCallsPermission().", e); if (!isSystemProcess()) { e.rethrowAsRuntimeException(); } diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 7599e189cc37..d72f8aa82ddb 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -20,7 +20,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.telecom.CallAudioState; -import android.telecom.CallScreeningService; +import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.Logging.Session; import android.telecom.PhoneAccountHandle; @@ -119,9 +119,9 @@ oneway interface IConnectionService { void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo); - void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, - in CallScreeningService.ParcelableCallResponse callScreeningResponse, - boolean isResponseFromSystemDialer, in Session.Info sessionInfo); + void onCallFilteringCompleted(String callId, + in Connection.CallFilteringCompletionInfo completionInfo, + in Session.Info sessionInfo); void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 88ef1b09f6b1..eb106b50f69d 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -179,9 +179,9 @@ interface ITelecomService { boolean isInCall(String callingPackage, String callingFeatureId); /** - * @see TelecomServiceImpl#hasCompanionInCallServiceAccess + * @see TelecomServiceImpl#hasManageOngoingCallsPermission */ - boolean hasCompanionInCallServiceAccess(String callingPackage); + boolean hasManageOngoingCallsPermission(String callingPackage); /** * @see TelecomServiceImpl#isInManagedCall diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index c6757fba4d53..4ae11b8458cb 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -111,6 +111,7 @@ public class Annotation { public @interface NetworkType { } + // TODO(b/180542000): remove and replace references with @ApnSetting.ApnType @IntDef(flag = true, prefix = {"TYPE_"}, value = { ApnSetting.TYPE_DEFAULT, ApnSetting.TYPE_MMS, @@ -124,6 +125,7 @@ public class Annotation { ApnSetting.TYPE_EMERGENCY, ApnSetting.TYPE_MCX, ApnSetting.TYPE_XCAP, + // ApnSetting.TYPE_ENTERPRISE }) @Retention(RetentionPolicy.SOURCE) public @interface ApnType { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 993f242b479d..0d5a2497329c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1721,8 +1721,14 @@ public class CarrierConfigManager { * Configs used for APN setup. */ public static final class Apn { - /** Prefix of all Apn.KEY_* constants. */ - private static final String KEY_PREFIX = "apn."; + /** + * Prefix of all Apn.KEY_* constants. + * + * @deprecated Since KEY_PREFIX is unnecessary to public, it will modify to private + * next android generation. + */ + @Deprecated + public static final String KEY_PREFIX = "apn."; /** IPv4 internet protocol */ public static final String PROTOCOL_IPV4 = "IP"; @@ -5364,7 +5370,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY, new String[0]); sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] { - "default:0", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2", + "default:0", "enterprise:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2", "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3" }); sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 99a77ae5d133..c8ed82cd2a3f 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -917,6 +917,10 @@ public final class DataFailCause { public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB; /** Data call fail due to the slice not being allowed for the data call. */ public static final int SLICE_REJECTED = 0x8CC; + /** No matching rule available for the request, and match-all rule is not allowed for it. */ + public static final int MATCH_ALL_RULE_NOT_ALLOWED = 0x8CD; + /** If connection failed for all matching URSP rules. */ + public static final int ALL_MATCHING_RULES_FAILED = 0x8CE; //IKE error notifications message as specified in 3GPP TS 24.302 (Section 8.1.2.2). diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java new file mode 100644 index 000000000000..7c7eb9fbbeb2 --- /dev/null +++ b/telephony/java/android/telephony/RadioInterfaceCapabilities.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.util.ArraySet; + +/** + * Contains the set of supported capabilities that the Radio Interface supports on this device. + * + * @hide + */ +public class RadioInterfaceCapabilities { + + private final ArraySet<String> mSupportedCapabilities; + + + public RadioInterfaceCapabilities() { + mSupportedCapabilities = new ArraySet<>(); + } + + /** + * Marks a capability as supported + * + * @param capabilityName the name of the capability + */ + public void addSupportedCapability( + @TelephonyManager.RadioInterfaceCapability String capabilityName) { + mSupportedCapabilities.add(capabilityName); + } + + /** + * Whether the capability is supported + * + * @param capabilityName the name of the capability + */ + public boolean isSupported(String capabilityName) { + return mSupportedCapabilities.contains(capabilityName); + } +} diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java index af67ed279fab..fe7e5976b132 100644 --- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java @@ -187,7 +187,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { return mIsSystemThresholdReportingRequestedWhileIdle; } - /* + /** * @return the live token of the request * * @hide diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 6c013df66840..4926687f6724 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -2649,6 +2649,37 @@ public final class SmsManager { */ public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl, Bundle configOverrides, PendingIntent sentIntent) { + sendMultimediaMessage(context, contentUri, locationUrl, configOverrides, sentIntent, + 0L /* messageId */); + } + + /** + * Send an MMS message + * + * Same as {@link #sendMultimediaMessage(Context context, Uri contentUri, String locationUrl, + * Bundle configOverrides, PendingIntent sentIntent)}, but adds an optional messageId. + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the MMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the + * conditions where this operation may fail. + * </p> + * + * @param context application context + * @param contentUri the content Uri from which the message pdu will be read + * @param locationUrl the optional location url where message should be sent to + * @param configOverrides the carrier-specific messaging configuration values to override for + * sending the message. + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed + * @param messageId an id that uniquely identifies the message requested to be sent. + * Used for logging and diagnostics purposes. The id may be 0. + * @throws IllegalArgumentException if contentUri is empty + */ + public void sendMultimediaMessage(@NonNull Context context, @NonNull Uri contentUri, + @Nullable String locationUrl, @Nullable Bundle configOverrides, + @Nullable PendingIntent sentIntent, long messageId) { if (contentUri == null) { throw new IllegalArgumentException("Uri contentUri null"); } @@ -2658,7 +2689,7 @@ public final class SmsManager { @Override public void onSuccess(int subId) { m.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides, - sentIntent, 0L /* messageId */); + sentIntent, messageId); } @Override @@ -2692,6 +2723,39 @@ public final class SmsManager { */ public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent) { + downloadMultimediaMessage(context, locationUrl, contentUri, configOverrides, + downloadedIntent, 0L /* messageId */); + } + + /** + * Download an MMS message from carrier by a given location URL + * + * Same as {@link #downloadMultimediaMessage(Context context, String locationUrl, + * Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent)}, + * but adds an optional messageId. + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail downloading the MMS message because no + * suitable default subscription could be found. In this case, if {@code downloadedIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the + * conditions where this operation may fail. + * </p> + * + * @param context application context + * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained + * from the MMS WAP push notification + * @param contentUri the content uri to which the downloaded pdu will be written + * @param configOverrides the carrier-specific messaging configuration values to override for + * downloading the message. + * @param downloadedIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is downloaded, or the download is failed + * @param messageId an id that uniquely identifies the message requested to be downloaded. + * Used for logging and diagnostics purposes. The id may be 0. + * @throws IllegalArgumentException if locationUrl or contentUri is empty + */ + public void downloadMultimediaMessage(@NonNull Context context, @NonNull String locationUrl, + @NonNull Uri contentUri, @Nullable Bundle configOverrides, + @Nullable PendingIntent downloadedIntent, long messageId) { if (TextUtils.isEmpty(locationUrl)) { throw new IllegalArgumentException("Empty MMS location URL"); } @@ -2704,7 +2768,7 @@ public final class SmsManager { @Override public void onSuccess(int subId) { m.downloadMultimediaMessage(subId, locationUrl, contentUri, configOverrides, - downloadedIntent, 0L /* messageId */); + downloadedIntent, messageId); } @Override diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f64f4283b66a..431fc484eb68 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -131,10 +131,12 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -8497,11 +8499,6 @@ public class TelephonyManager { * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). - * <p> - * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} - * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then - * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, - * setPreferredNetworkTypesBitmap is used instead. * * @param subId the id of the subscription to set the preferred network type for. * @param networkType the preferred network type @@ -8535,11 +8532,6 @@ public class TelephonyManager { * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). - * <p> - * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} - * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then - * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, - * setPreferredNetworkTypesBitmap is used instead. * * @param networkTypeBitmask The bitmask of preferred network types. * @return true on success; false on any failure. @@ -8566,11 +8558,6 @@ public class TelephonyManager { * Set the allowed network types of the device. This is for carrier or privileged apps to * enable/disable certain network types on the device. The user preferred network types should * be set through {@link #setPreferredNetworkTypeBitmask}. - * <p> - * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} - * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then - * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, - * setPreferredNetworkTypesBitmap is used instead. * * @param allowedNetworkTypes The bitmask of allowed network types. * @return true on success; false on any failure. @@ -8655,12 +8642,12 @@ public class TelephonyManager { * {@link #ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G} * </ol> * This API will result in allowing an intersection of allowed network types for all reasons, - * including the configuration done through other reasons. - * <p> - * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} - * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then - * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, - * setPreferredNetworkTypesBitmap is used instead. + * including the configuration done through {@link setAllowedNetworkTypes}. + * While this API and {@link setAllowedNetworkTypes} is controlling allowed network types + * on device, user preference will still be set through {@link #setPreferredNetworkTypeBitmask}. + * Thus resultant network type configured on modem will be an intersection of the network types + * from setAllowedNetworkTypesForReason, {@link setAllowedNetworkTypes} + * and {@link #setPreferredNetworkTypeBitmask}. * * @param reason the reason the allowed network type change is taking place * @param allowedNetworkTypes The bitmask of allowed network types. @@ -14894,24 +14881,10 @@ public class TelephonyManager { public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE"; - /** - * Indicates whether {@link #setPreferredNetworkType}, {@link - * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and - * {@link #setAllowedNetworkTypesForReason} rely on - * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio - * interface. - * - * @hide - */ - @SystemApi - public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = - "CAPABILITY_ALLOWED_NETWORK_TYPES_USED"; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @StringDef(prefix = "CAPABILITY_", value = { CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE, - CAPABILITY_ALLOWED_NETWORK_TYPES_USED, }) public @interface RadioInterfaceCapability {} diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index d58fa912dce2..b503733f8de9 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -28,8 +28,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.provider.Telephony; import android.provider.Telephony.Carriers; -import android.telephony.Annotation; -import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; import android.telephony.ServiceState; import android.telephony.TelephonyManager; @@ -116,6 +114,31 @@ public class ApnSetting implements Parcelable { public static final int TYPE_MCX = ApnTypes.MCX; /** APN type for XCAP. */ public static final int TYPE_XCAP = ApnTypes.XCAP; + /** + * APN type for ENTERPRISE. + * @hide + */ + public static final int TYPE_ENTERPRISE = TYPE_XCAP << 1; + + /** @hide */ + @IntDef(flag = true, prefix = {"TYPE_"}, value = { + TYPE_DEFAULT, + TYPE_MMS, + TYPE_SUPL, + TYPE_DUN, + TYPE_HIPRI, + TYPE_FOTA, + TYPE_IMS, + TYPE_CBS, + TYPE_IA, + TYPE_EMERGENCY, + TYPE_MCX, + TYPE_XCAP, + TYPE_ENTERPRISE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ApnType { + } // Possible values for authentication types. /** No authentication type. */ @@ -151,6 +174,7 @@ public class ApnSetting implements Parcelable { TYPE_MMS_STRING, TYPE_SUPL_STRING, TYPE_XCAP_STRING, + TYPE_ENTERPRISE_STRING, }, prefix = "TYPE_", suffix = "_STRING") @Retention(RetentionPolicy.SOURCE) public @interface ApnTypeString {} @@ -291,6 +315,12 @@ public class ApnSetting implements Parcelable { @SystemApi public static final String TYPE_XCAP_STRING = "xcap"; + /** + * APN type for ENTERPRISE traffic. + * @hide + */ + public static final String TYPE_ENTERPRISE_STRING = "enterprise"; + /** @hide */ @IntDef(prefix = { "AUTH_TYPE_" }, value = { @@ -370,6 +400,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_STRING_MAP.put(TYPE_EMERGENCY_STRING, TYPE_EMERGENCY); APN_TYPE_STRING_MAP.put(TYPE_MCX_STRING, TYPE_MCX); APN_TYPE_STRING_MAP.put(TYPE_XCAP_STRING, TYPE_XCAP); + APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE); APN_TYPE_INT_MAP = new ArrayMap<>(); APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING); @@ -384,6 +415,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, TYPE_EMERGENCY_STRING); APN_TYPE_INT_MAP.put(TYPE_MCX, TYPE_MCX_STRING); APN_TYPE_INT_MAP.put(TYPE_XCAP, TYPE_XCAP_STRING); + APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING); PROTOCOL_STRING_MAP = new ArrayMap<>(); PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP); @@ -1490,7 +1522,7 @@ public class ApnSetting implements Parcelable { * @hide */ @SystemApi - public static @NonNull @ApnTypeString String getApnTypeString(@Annotation.ApnType int apnType) { + public static @NonNull @ApnTypeString String getApnTypeString(@ApnType int apnType) { if (apnType == TYPE_ALL) { return "*"; } @@ -1503,7 +1535,7 @@ public class ApnSetting implements Parcelable { * when provided with an invalid int for compatibility purposes. * @hide */ - public static @NonNull String getApnTypeStringInternal(@Annotation.ApnType int apnType) { + public static @NonNull String getApnTypeStringInternal(@ApnType int apnType) { String result = getApnTypeString(apnType); return TextUtils.isEmpty(result) ? "Unknown" : result; } @@ -1517,7 +1549,7 @@ public class ApnSetting implements Parcelable { * @hide */ @SystemApi - public static @Annotation.ApnType int getApnTypeInt(@NonNull @ApnTypeString String apnType) { + public static @ApnType int getApnTypeInt(@NonNull @ApnTypeString String apnType) { return APN_TYPE_STRING_MAP.getOrDefault(apnType.toLowerCase(), 0); } @@ -2162,7 +2194,7 @@ public class ApnSetting implements Parcelable { public ApnSetting build() { if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX - | TYPE_XCAP)) == 0 + | TYPE_XCAP | TYPE_ENTERPRISE)) == 0 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) { return null; } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index bd4bf0740ca1..a76422977cb6 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -138,6 +138,7 @@ public final class DataCallResponse implements Parcelable { private final Qos mDefaultQos; private final List<QosBearerSession> mQosBearerSessions; private final SliceInfo mSliceInfo; + private final List<TrafficDescriptor> mTrafficDescriptors; /** * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. @@ -189,6 +190,7 @@ public final class DataCallResponse implements Parcelable { mDefaultQos = null; mQosBearerSessions = new ArrayList<>(); mSliceInfo = null; + mTrafficDescriptors = new ArrayList<>(); } private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id, @@ -198,7 +200,7 @@ public final class DataCallResponse implements Parcelable { @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, @HandoverFailureMode int handoverFailureMode, int pduSessionId, @Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions, - @Nullable SliceInfo sliceInfo) { + @Nullable SliceInfo sliceInfo, @Nullable List<TrafficDescriptor> trafficDescriptors) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; @@ -219,8 +221,11 @@ public final class DataCallResponse implements Parcelable { mHandoverFailureMode = handoverFailureMode; mPduSessionId = pduSessionId; mDefaultQos = defaultQos; - mQosBearerSessions = qosBearerSessions; + mQosBearerSessions = (qosBearerSessions == null) + ? new ArrayList<>() : new ArrayList<>(qosBearerSessions); mSliceInfo = sliceInfo; + mTrafficDescriptors = (trafficDescriptors == null) + ? new ArrayList<>() : new ArrayList<>(trafficDescriptors); } /** @hide */ @@ -249,6 +254,8 @@ public final class DataCallResponse implements Parcelable { mQosBearerSessions = new ArrayList<>(); source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader()); mSliceInfo = source.readParcelable(SliceInfo.class.getClassLoader()); + mTrafficDescriptors = new ArrayList<>(); + source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader()); } /** @@ -381,7 +388,6 @@ public final class DataCallResponse implements Parcelable { * * @hide */ - @Nullable public Qos getDefaultQos() { return mDefaultQos; @@ -406,6 +412,14 @@ public final class DataCallResponse implements Parcelable { return mSliceInfo; } + /** + * @return The traffic descriptors related to this data connection. + */ + @NonNull + public List<TrafficDescriptor> getTrafficDescriptors() { + return mTrafficDescriptors; + } + @NonNull @Override public String toString() { @@ -429,6 +443,7 @@ public final class DataCallResponse implements Parcelable { .append(" defaultQos=").append(mDefaultQos) .append(" qosBearerSessions=").append(mQosBearerSessions) .append(" sliceInfo=").append(mSliceInfo) + .append(" trafficDescriptors=").append(mTrafficDescriptors) .append("}"); return sb.toString(); } @@ -443,15 +458,22 @@ public final class DataCallResponse implements Parcelable { DataCallResponse other = (DataCallResponse) o; - final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null) ? - mDefaultQos == other.mDefaultQos : - mDefaultQos.equals(other.mDefaultQos); + final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null) + ? mDefaultQos == other.mDefaultQos + : mDefaultQos.equals(other.mDefaultQos); - final boolean isQosBearerSessionsSame = (mQosBearerSessions == null || mQosBearerSessions == null) ? - mQosBearerSessions == other.mQosBearerSessions : - mQosBearerSessions.size() == other.mQosBearerSessions.size() + final boolean isQosBearerSessionsSame = + (mQosBearerSessions == null || other.mQosBearerSessions == null) + ? mQosBearerSessions == other.mQosBearerSessions + : mQosBearerSessions.size() == other.mQosBearerSessions.size() && mQosBearerSessions.containsAll(other.mQosBearerSessions); + final boolean isTrafficDescriptorsSame = + (mTrafficDescriptors == null || other.mTrafficDescriptors == null) + ? mTrafficDescriptors == other.mTrafficDescriptors + : mTrafficDescriptors.size() == other.mTrafficDescriptors.size() + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); + return mCause == other.mCause && mSuggestedRetryTime == other.mSuggestedRetryTime && mId == other.mId @@ -473,7 +495,8 @@ public final class DataCallResponse implements Parcelable { && mPduSessionId == other.mPduSessionId && isQosSame && isQosBearerSessionsSame - && Objects.equals(mSliceInfo, other.mSliceInfo); + && Objects.equals(mSliceInfo, other.mSliceInfo) + && isTrafficDescriptorsSame; } @Override @@ -481,7 +504,7 @@ public final class DataCallResponse implements Parcelable { return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos, - mQosBearerSessions, mSliceInfo); + mQosBearerSessions, mSliceInfo, mTrafficDescriptors); } @Override @@ -517,6 +540,7 @@ public final class DataCallResponse implements Parcelable { } dest.writeList(mQosBearerSessions); dest.writeParcelable(mSliceInfo, flags); + dest.writeList(mTrafficDescriptors); } public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR = @@ -602,6 +626,8 @@ public final class DataCallResponse implements Parcelable { private SliceInfo mSliceInfo; + private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>(); + /** * Default constructor for Builder. */ @@ -841,6 +867,24 @@ public final class DataCallResponse implements Parcelable { } /** + * The traffic descriptors for this data connection, as defined in 3GPP TS 24.526 + * Section 5.2. They are used for URSP traffic matching as described in 3GPP TS 24.526 + * Section 4.2.2. They includes an optional DNN, which, if present, must be used for traffic + * matching; it does not specify the end point to be used for the data call. The end point + * is specified by {@link DataProfile}, which must be used as the end point if one is not + * specified through URSP rules. + * + * @param trafficDescriptors the traffic descriptors for the data call. + * + * @return The same instance of the builder. + */ + public @NonNull Builder setTrafficDescriptors( + @NonNull List<TrafficDescriptor> trafficDescriptors) { + mTrafficDescriptors = trafficDescriptors; + return this; + } + + /** * Build the DataCallResponse. * * @return the DataCallResponse object. @@ -849,7 +893,7 @@ public final class DataCallResponse implements Parcelable { return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, - mDefaultQos, mQosBearerSessions, mSliceInfo); + mDefaultQos, mQosBearerSessions, mSliceInfo, mTrafficDescriptors); } } } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 484c318c1ac0..f5f29c65b7cd 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -200,6 +200,17 @@ public abstract class DataService extends Service { * handover is occurring from EPDG to 5G. If the slice passed is rejected, then * {@link DataCallResponse#getCause()} is * {@link android.telephony.DataFailCause#SLICE_REJECTED}. + * @param trafficDescriptor {@link TrafficDescriptor} for which data connection needs to be + * established. It is used for URSP traffic matching as described in 3GPP TS 24.526 + * Section 4.2.2. It includes an optional DNN which, if present, must be used for + * traffic matching; it does not specify the end point to be used for the data call. + * @param matchAllRuleAllowed Indicates if using default match-all URSP rule for this + * request is allowed. If false, this request must not use the match-all URSP rule + * and if a non-match-all rule is not found (or if URSP rules are not available) then + * {@link DataCallResponse#getCause()} is + * {@link android.telephony.DataFailCause#MATCH_ALL_RULE_NOT_ALLOWED}. This is needed + * as some requests need to have a hard failure if the intention cannot be met, + * for example, a zero-rating slice. * @param callback The result callback for this request. */ public void setupDataCall(int accessNetworkType, @NonNull DataProfile dataProfile, @@ -207,6 +218,7 @@ public abstract class DataService extends Service { @SetupDataReason int reason, @Nullable LinkProperties linkProperties, @IntRange(from = 0, to = 15) int pduSessionId, @Nullable SliceInfo sliceInfo, + @Nullable TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, @NonNull DataServiceCallback callback) { /* Call the old version since the new version isn't supported */ setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming, reason, @@ -403,10 +415,13 @@ public abstract class DataService extends Service { public final LinkProperties linkProperties; public final int pduSessionId; public final SliceInfo sliceInfo; + public final TrafficDescriptor trafficDescriptor; + public final boolean matchAllRuleAllowed; public final IDataServiceCallback callback; SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming, - boolean allowRoaming, int reason, LinkProperties linkProperties, - int pduSessionId, SliceInfo sliceInfo, IDataServiceCallback callback) { + boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId, + SliceInfo sliceInfo, TrafficDescriptor trafficDescriptor, + boolean matchAllRuleAllowed, IDataServiceCallback callback) { this.accessNetworkType = accessNetworkType; this.dataProfile = dataProfile; this.isRoaming = isRoaming; @@ -415,6 +430,8 @@ public abstract class DataService extends Service { this.reason = reason; this.pduSessionId = pduSessionId; this.sliceInfo = sliceInfo; + this.trafficDescriptor = trafficDescriptor; + this.matchAllRuleAllowed = matchAllRuleAllowed; this.callback = callback; } } @@ -525,7 +542,8 @@ public abstract class DataService extends Service { setupDataCallRequest.dataProfile, setupDataCallRequest.isRoaming, setupDataCallRequest.allowRoaming, setupDataCallRequest.reason, setupDataCallRequest.linkProperties, setupDataCallRequest.pduSessionId, - setupDataCallRequest.sliceInfo, + setupDataCallRequest.sliceInfo, setupDataCallRequest.trafficDescriptor, + setupDataCallRequest.matchAllRuleAllowed, (setupDataCallRequest.callback != null) ? new DataServiceCallback(setupDataCallRequest.callback) : null); @@ -690,11 +708,12 @@ public abstract class DataService extends Service { public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId, SliceInfo sliceInfo, + TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, IDataServiceCallback callback) { mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0, new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo, - callback)) + trafficDescriptor, matchAllRuleAllowed, callback)) .sendToTarget(); } diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl index e0b9a1a9bb5a..81f5fd3b69a9 100644 --- a/telephony/java/android/telephony/data/IDataService.aidl +++ b/telephony/java/android/telephony/data/IDataService.aidl @@ -20,6 +20,7 @@ import android.net.LinkProperties; import android.telephony.data.DataProfile; import android.telephony.data.IDataServiceCallback; import android.telephony.data.SliceInfo; +import android.telephony.data.TrafficDescriptor; /** * {@hide} @@ -30,7 +31,9 @@ oneway interface IDataService void removeDataServiceProvider(int slotId); void setupDataCall(int slotId, int accessNetwork, in DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, int reason, in LinkProperties linkProperties, - int pduSessionId, in SliceInfo sliceInfo, IDataServiceCallback callback); + int pduSessionId, in SliceInfo sliceInfo, + in TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, + IDataServiceCallback callback); void deactivateDataCall(int slotId, int cid, int reason, IDataServiceCallback callback); void setInitialAttachApn(int slotId, in DataProfile dataProfile, boolean isRoaming, IDataServiceCallback callback); diff --git a/telephony/java/android/telephony/data/TrafficDescriptor.aidl b/telephony/java/android/telephony/data/TrafficDescriptor.aidl new file mode 100644 index 000000000000..a9c7604a91b6 --- /dev/null +++ b/telephony/java/android/telephony/data/TrafficDescriptor.aidl @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** @hide */ +package android.telephony.data; + +parcelable TrafficDescriptor; diff --git a/telephony/java/android/telephony/data/TrafficDescriptor.java b/telephony/java/android/telephony/data/TrafficDescriptor.java new file mode 100644 index 000000000000..480379d641b6 --- /dev/null +++ b/telephony/java/android/telephony/data/TrafficDescriptor.java @@ -0,0 +1,111 @@ +/* + * 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.telephony.data; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2. It is used for URSP traffic + * matching as described in 3GPP TS 24.526 Section 4.2.2. It includes an optional DNN, which, + * if present, must be used for traffic matching; it does not specify the end point to be used for + * the data call. + * @hide + */ +@SystemApi +public final class TrafficDescriptor implements Parcelable { + private final String mDnn; + private final String mOsAppId; + + private TrafficDescriptor(@NonNull Parcel in) { + mDnn = in.readString(); + mOsAppId = in.readString(); + } + + /** + * Create a traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2 + * @param dnn optional DNN, which must be used for traffic matching, if present + * @param osAppId OsId + osAppId of the traffic descriptor + */ + public TrafficDescriptor(@Nullable String dnn, @Nullable String osAppId) { + mDnn = dnn; + mOsAppId = osAppId; + } + + /** + * DNN stands for Data Network Name and represents an APN as defined in 3GPP TS 23.003. + * @return the DNN of this traffic descriptor. + */ + public @Nullable String getDnn() { + return mDnn; + } + + /** + * OsAppId represents the OsId + OsAppId as defined in 3GPP TS 24.526 Section 5.2. + * @return the OS App ID of this traffic descriptor. + */ + public @Nullable String getOsAppId() { + return mOsAppId; + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull @Override + public String toString() { + return "TrafficDescriptor={mDnn=" + mDnn + ", mOsAppId=" + mOsAppId + "}"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mDnn); + dest.writeString(mOsAppId); + } + + public static final @NonNull Parcelable.Creator<TrafficDescriptor> CREATOR = + new Parcelable.Creator<TrafficDescriptor>() { + @Override + public @NonNull TrafficDescriptor createFromParcel(@NonNull Parcel source) { + return new TrafficDescriptor(source); + } + + @Override + public @NonNull TrafficDescriptor[] newArray(int size) { + return new TrafficDescriptor[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrafficDescriptor that = (TrafficDescriptor) o; + return Objects.equals(mDnn, that.mDnn) && Objects.equals(mOsAppId, that.mOsAppId); + } + + @Override + public int hashCode() { + return Objects.hash(mDnn, mOsAppId); + } +} diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 5f8e93d02a00..8ad40ed1032c 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -57,10 +57,10 @@ public class ImsEcbmImplBase { } else if (listener != null && mListener == null) { mListener = listener; } else { - // Fail fast here instead of silently overwriting the listener to another - // listener due to another connection connecting. - throw new IllegalStateException("ImsEcbmImplBase: Listener already set by " - + "another connection."); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mListener = listener; } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 8e961acc7b36..ec1c7b3a92a8 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -62,10 +62,10 @@ public class ImsMultiEndpointImplBase { } else if (listener != null && mListener == null) { mListener = listener; } else { - // Fail fast here instead of silently overwriting the listener to another - // listener due to another connection connecting. - throw new IllegalStateException("ImsMultiEndpointImplBase: Listener already" - + " set by another connection."); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mListener = listener; } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 83b89aa8e814..eb3e8ed5a8e4 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -224,11 +224,10 @@ public class ImsUtImplBase { } else if (listener != null && mUtListener == null) { mUtListener = new ImsUtListener(listener); } else { - // This is a limitation of the current API surface, there can only be one - // listener connected. Fail fast instead of silently overwriting the other - // listener. - throw new IllegalStateException("ImsUtImplBase#setListener: listener already " - + "set by another connected interface!"); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mUtListener = new ImsUtListener(listener); } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 5c255243f52b..0048d53f9172 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2055,6 +2055,11 @@ interface ITelephony { int setImsProvisioningString(int subId, int key, String value); /** + * Start emergency callback mode for testing. + */ + void startEmergencyCallbackMode(); + + /** * Update Emergency Number List for Test Mode. */ void updateEmergencyNumberListTestMode(int action, in EmergencyNumber num); diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index 151187c5071f..3a99f0e010c6 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -126,6 +126,7 @@ public class PhoneConstants { * connections.<br/> * APN_TYPE_ALL is a special type to indicate that this APN entry can * service all data connections. + * TODO: remove these and use the reference to ApnSetting.TYPE_XXX_STRING instead */ public static final String APN_TYPE_ALL = ApnSetting.TYPE_ALL_STRING; /** APN type for default data traffic */ @@ -153,20 +154,8 @@ public class PhoneConstants { public static final String APN_TYPE_MCX = ApnSetting.TYPE_MCX_STRING; /** APN type for XCAP */ public static final String APN_TYPE_XCAP = ApnSetting.TYPE_XCAP_STRING; - /** Array of all APN types */ - public static final String[] APN_TYPES = {APN_TYPE_DEFAULT, - APN_TYPE_MMS, - APN_TYPE_SUPL, - APN_TYPE_DUN, - APN_TYPE_HIPRI, - APN_TYPE_FOTA, - APN_TYPE_IMS, - APN_TYPE_CBS, - APN_TYPE_IA, - APN_TYPE_EMERGENCY, - APN_TYPE_MCX, - APN_TYPE_XCAP, - }; + // /** APN type for enterprise */ + // public static final String APN_TYPE_ENTERPRISE = ApnSetting.TYPE_ENTERPRISE_STRING; public static final int RIL_CARD_MAX_APPS = 8; diff --git a/test-base/Android.bp b/test-base/Android.bp index 0b7a3981a403..9bd639b63ae0 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -49,6 +49,12 @@ java_sdk_library { compile_dex: true, default_to_stubs: true, + + // Additional hiddenapi annotations are provided in a separate module. + // TODO(b/180295980) - investigate whether this can be removed + hiddenapi_additional_annotations: [ + "android.test.base-hiddenapi-annotations", + ], } // Build the android.test.base_static library @@ -91,8 +97,9 @@ java_library_static { // =============================================== // This contains the android.test classes from android.test.base plus // the com.android.internal.util.Predicate[s] classes. This is only -// intended for inclusion in android.test.legacy and must not be used -// elsewhere. +// intended for inclusion in android.test.legacy and in +// android.test.base-hiddenapi-annotations to avoid a dependency cycle and must +// not be used elsewhere. java_library_static { name: "android.test.base-minus-junit", diff --git a/test-base/hiddenapi/Android.bp b/test-base/hiddenapi/Android.bp index d4f52d0fc6cd..1466590ef311 100644 --- a/test-base/hiddenapi/Android.bp +++ b/test-base/hiddenapi/Android.bp @@ -14,11 +14,6 @@ // limitations under the License. // -// Provided solely to contribute information about which hidden parts of the android.test.base -// library are used by apps. The source files are stubs of the actual files in ../src which use the -// UnsupportedAppUsage annotation to tag those methods that are accessible via the hiddenapi. -// Relies on the convention that modules with name <x>-hiddenapi provide hiddenapi information for -// module <x> that is on the bootclasspath. package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -28,14 +23,20 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +// Provided solely to contribute information about which hidden parts of the android.test.base +// library are used by apps. The source files are stubs of the actual files in ../src which use the +// UnsupportedAppUsage annotation to tag those methods that are accessible via the hiddenapi. java_library { - name: "android.test.base-hiddenapi", + name: "android.test.base-hiddenapi-annotations", compile_dex: true, srcs: ["src/**/*.java"], libs: [ - "android.test.base", + // Use this instead of `android.test.base` to avoid a dependency cycle + // as `android.test.base` depends on this. + "android.test.base-minus-junit", + "junit", "unsupportedappusage", ], } diff --git a/tests/BatteryStatsPerfTest/Android.bp b/tests/BatteryStatsPerfTest/Android.bp index 58ccec705419..5233a5b8654e 100644 --- a/tests/BatteryStatsPerfTest/Android.bp +++ b/tests/BatteryStatsPerfTest/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "BatteryStatsPerfTests", srcs: ["src/**/*.java"], diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 7d29cdd2b5c5..a8b7b057fe24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -21,8 +21,6 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME -const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider" -const val DOCKED_STACK_DIVIDER = "DockedStackDivider" const val WALLPAPER_TITLE = "Wallpaper" fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { @@ -64,9 +62,9 @@ fun FlickerTestParameter.wallpaperWindowBecomesVisible() { fun FlickerTestParameter.wallpaperWindowBecomesInvisible() { assertWm { - this.showsBelowAppWindow("Wallpaper") + this.showsBelowAppWindow(WALLPAPER_TITLE) .then() - .hidesBelowAppWindow("Wallpaper") + .hidesBelowAppWindow(WALLPAPER_TITLE) } } @@ -103,19 +101,19 @@ fun FlickerTestParameter.noUncoveredRegions( if (allStates) { assertLayers { if (startingBounds == endingBounds) { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) } else { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) .then() - .coversAtLeastRegion(endingBounds) + .coversAtLeast(endingBounds) } } } else { assertLayersStart { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) } assertLayersEnd { - this.coversAtLeastRegion(endingBounds) + this.coversAtLeast(endingBounds) } } } @@ -124,15 +122,15 @@ fun FlickerTestParameter.noUncoveredRegions( fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertLayers { - this.showsLayer(NAV_BAR_LAYER_NAME) + this.isVisible(NAV_BAR_LAYER_NAME) .then() - .hidesLayer(NAV_BAR_LAYER_NAME) + .isInvisible(NAV_BAR_LAYER_NAME) .then() - .showsLayer(NAV_BAR_LAYER_NAME) + .isVisible(NAV_BAR_LAYER_NAME) } } else { assertLayers { - this.showsLayer(NAV_BAR_LAYER_NAME) + this.isVisible(NAV_BAR_LAYER_NAME) } } } @@ -141,15 +139,15 @@ fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = fal fun FlickerTestParameter.statusBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertLayers { - this.showsLayer(STATUS_BAR_WINDOW_NAME) + this.isVisible(STATUS_BAR_WINDOW_NAME) .then() - hidesLayer(STATUS_BAR_WINDOW_NAME) + .isInvisible(STATUS_BAR_WINDOW_NAME) .then() - .showsLayer(STATUS_BAR_WINDOW_NAME) + .isVisible(STATUS_BAR_WINDOW_NAME) } } else { assertLayers { - this.showsLayer(STATUS_BAR_WINDOW_NAME) + this.isVisible(STATUS_BAR_WINDOW_NAME) } } } @@ -163,10 +161,10 @@ fun FlickerTestParameter.navBarLayerRotatesAndScales( val endingPos = WindowUtils.getNavigationBarPosition(endRotation) assertLayersStart { - this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos) + this.coversExactly(startingPos, NAV_BAR_LAYER_NAME) } assertLayersEnd { - this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos) + this.coversExactly(endingPos, NAV_BAR_LAYER_NAME) } } @@ -179,10 +177,10 @@ fun FlickerTestParameter.statusBarLayerRotatesScales( val endingPos = WindowUtils.getStatusBarPosition(endRotation) assertLayersStart { - this.hasVisibleRegion(STATUS_BAR_WINDOW_NAME, startingPos) + this.coversExactly(startingPos, STATUS_BAR_WINDOW_NAME) } assertLayersEnd { - this.hasVisibleRegion(STATUS_BAR_WINDOW_NAME, endingPos) + this.coversExactly(endingPos, STATUS_BAR_WINDOW_NAME) } } @@ -197,39 +195,41 @@ fun FlickerTestParameter.visibleLayersShownMoreThanOneConsecutiveEntry( fun FlickerTestParameter.appLayerReplacesWallpaperLayer(appName: String) { assertLayers { - this.showsLayer("Wallpaper") + this.isVisible(WALLPAPER_TITLE) .then() - .replaceVisibleLayer("Wallpaper", appName) + .isInvisible(WALLPAPER_TITLE) + .isVisible(appName) } } fun FlickerTestParameter.wallpaperLayerReplacesAppLayer(testApp: IAppHelper) { assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .replaceVisibleLayer(testApp.getPackage(), WALLPAPER_TITLE) + .isInvisible(testApp.getPackage()) + .isVisible(WALLPAPER_TITLE) } } fun FlickerTestParameter.layerAlwaysVisible(packageName: String) { assertLayers { - this.showsLayer(packageName) + this.isVisible(packageName) } } fun FlickerTestParameter.layerBecomesVisible(packageName: String) { assertLayers { - this.hidesLayer(packageName) + this.isInvisible(packageName) .then() - .showsLayer(packageName) + .isVisible(packageName) } } fun FlickerTestParameter.layerBecomesInvisible(packageName: String) { assertLayers { - this.showsLayer(packageName) + this.isVisible(packageName) .then() - .hidesLayer(packageName) + .isInvisible(packageName) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 38a88d372da4..26afb794bb06 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -153,31 +153,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun navBarLayerIsAlwaysVisible() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerIsAlwaysVisible() - } - - @FlakyTest - @Test - fun navBarLayerIsAlwaysVisible_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerIsAlwaysVisible() - } + fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerIsAlwaysVisible() - } - - @FlakyTest - @Test - fun statusBarLayerIsAlwaysVisible_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerIsAlwaysVisible() - } + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 476708c42c4c..2c4c627a444d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -17,9 +17,8 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -85,55 +84,55 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE)) - @Postsubmit + @Presubmit @Test fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) - @FlakyTest + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - @FlakyTest + @Presubmit @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - @Postsubmit + @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() = testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() - @Postsubmit + @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index ac140f505076..2bcdcd9c8219 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -91,63 +91,54 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE)) - @Postsubmit + @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp) - @Postsubmit + @Presubmit @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - @Postsubmit + @Presubmit @Test fun statusBarLayerRotatesScales() { Assume.assumeFalse(testSpec.isRotated) @@ -171,7 +162,8 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0)) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index 212644c04505..3dfa31d64f8c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -23,23 +23,23 @@ const val IME_WINDOW_TITLE = "InputMethod" fun FlickerTestParameter.imeLayerBecomesVisible() { assertLayers { - this.hidesLayer(IME_WINDOW_TITLE) + this.isInvisible(IME_WINDOW_TITLE) .then() - .showsLayer(IME_WINDOW_TITLE) + .isVisible(IME_WINDOW_TITLE) } } fun FlickerTestParameter.imeLayerBecomesInvisible() { assertLayers { - this.showsLayer(IME_WINDOW_TITLE) + this.isVisible(IME_WINDOW_TITLE) .then() - .hidesLayer(IME_WINDOW_TITLE) + .isInvisible(IME_WINDOW_TITLE) } } fun FlickerTestParameter.imeAppLayerIsAlwaysVisible(testApp: IAppHelper) { assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) } } @@ -83,9 +83,8 @@ fun FlickerTestParameter.imeAppWindowBecomesInvisible(testApp: IAppHelper) { fun FlickerTestParameter.imeAppLayerBecomesInvisible(testApp: IAppHelper) { assertLayers { - this.skipUntilFirstAssertion() - .showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .hidesLayer(testApp.getPackage()) + .isInvisible(testApp.getPackage()) } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index c7a5178a6693..008ba2f72b6c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -89,43 +89,43 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() - @Postsubmit + @Presubmit @Test fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) - @Postsubmit + @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() - @Postsubmit + @Presubmit @Test fun layerAlwaysVisible() = testSpec.layerAlwaysVisible(testApp.`package`) - @Postsubmit + @Presubmit @Test fun navBarLayerRotatesAndScales() { Assume.assumeFalse(testSpec.isRotated) @@ -139,7 +139,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) } - @Postsubmit + @Presubmit @Test fun statusBarLayerRotatesScales() { Assume.assumeFalse(testSpec.isRotated) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index e2a258aea239..18fac6a82de7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -148,17 +147,35 @@ class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { @Test fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) - @Postsubmit + @Presubmit @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + Assume.assumeFalse(testSpec.isRotated) testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest + @Test + fun visibleWindowsShownMoreThanOneConsecutiveEntry_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @Presubmit + @Test + fun visibleLayersShownMoreThanOneConsecutiveEntry() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } @FlakyTest @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = + fun visibleLayersShownMoreThanOneConsecutiveEntry_Flaky() { + Assume.assumeTrue(testSpec.isRotated) testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } - @FlakyTest(bugId = 141361128) + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(Surface.ROTATION_0, testSpec.config.endRotation) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 3cc509fe2b8e..20e4b88ff13b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,13 +16,13 @@ package com.android.server.wm.flicker.rotation -import android.os.Bundle import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.focusDoesNotChange import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -54,7 +54,15 @@ class ChangeAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { override val testApp = SimpleAppHelper(instrumentation) - override fun getAppLaunchParams(configuration: Bundle): Map<String, String> = emptyMap() + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + setup { + test { + testApp.launchViaIntent(wmHelper) + } + } + } @Presubmit @Test @@ -78,11 +86,11 @@ class ChangeAppRotationTest( @Test fun screenshotLayerBecomesInvisible() { testSpec.assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .showsLayer(SCREENSHOT_LAYER) + .isVisible(SCREENSHOT_LAYER) .then() - .showsLayer(testApp.getPackage()) + .isVisible(testApp.getPackage()) } } @@ -113,7 +121,7 @@ class ChangeAppRotationTest( @Test fun appLayerRotates_StartingPos() { testSpec.assertLayersStart { - this.hasVisibleRegion(testApp.getPackage(), startingPos) + this.coversExactly(startingPos, testApp.getPackage()) } } @@ -121,7 +129,7 @@ class ChangeAppRotationTest( @Test fun appLayerRotates_EndingPos() { testSpec.assertLayersEnd { - this.hasVisibleRegion(testApp.getPackage(), endingPos) + this.coversExactly(endingPos, testApp.getPackage()) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 04ab84dfcd8e..c391112a53ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.rotation import android.app.Instrumentation -import android.os.Bundle import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -31,36 +30,37 @@ import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation abstract class RotationTransition(protected val testSpec: FlickerTestParameter) { + protected abstract val testApp: StandardAppHelper + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected val startingPos get() = WindowUtils.getDisplayBounds(testSpec.config.startRotation) protected val endingPos get() = WindowUtils.getDisplayBounds(testSpec.config.endRotation) - protected abstract val testApp: StandardAppHelper - protected abstract fun getAppLaunchParams(configuration: Bundle): Map<String, String> + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + test { + device.wakeUpAndGoToHomeScreen() + } + eachRun { + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + test { + testApp.exit() + } + } + transitions { + this.setRotation(testSpec.config.endRotation) + } + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - val extras = getAppLaunchParams(testSpec.config) - testApp.launchViaIntent(wmHelper, stringExtras = extras) - } - eachRun { - this.setRotation(testSpec.config.startRotation) - } - } - teardown { - test { - testApp.exit() - } - } - transitions { - this.setRotation(testSpec.config.endRotation) - } + transition(testSpec.config) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index ef1aeadb31bc..fc5bcc7eef1b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.rotation -import android.os.Bundle import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -24,6 +23,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.focusDoesNotChange import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper @@ -58,9 +58,18 @@ class SeamlessAppRotationTest( ) : RotationTransition(testSpec) { override val testApp = SeamlessRotationAppHelper(instrumentation) - override fun getAppLaunchParams(configuration: Bundle): Map<String, String> = mapOf( - ActivityOptions.EXTRA_STARVE_UI_THREAD to configuration.starveUiThread.toString() - ) + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + setup { + test { + testApp.launchViaIntent(wmHelper, + stringExtras = mapOf( + ActivityOptions.EXTRA_STARVE_UI_THREAD to it.starveUiThread.toString()) + ) + } + } + } @Presubmit @Test @@ -115,7 +124,7 @@ class SeamlessAppRotationTest( @Test fun appLayerRotates() { testSpec.assertLayers { - this.hasVisibleRegion(testApp.`package`, startingPos) + this.coversExactly(startingPos, testApp.`package`) } } @@ -126,12 +135,14 @@ class SeamlessAppRotationTest( companion object { private val testFactory = FlickerTestParameterFactory.getInstance() - private val Bundle.starveUiThread - get() = this.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) + private val Map<String, Any?>.starveUiThread + get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean - private fun FlickerTestParameter.createConfig(starveUiThread: Boolean): Bundle { - val config = this.config.deepCopy() - config.putBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD, starveUiThread) + private fun FlickerTestParameter.createConfig( + starveUiThread: Boolean + ): MutableMap<String, Any?> { + val config = this.config.toMutableMap() + config[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread return config } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index a72b07c45dd8..335c8d0127eb 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "InputTests", srcs: ["src/**/*.kt"], diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt index f919a3eaf271..c19e5cc34611 100644 --- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt +++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt @@ -62,15 +62,12 @@ class ViewFrameInfoTest { fun testUpdateFrameInfoFromViewFrameInfo() { val frameInfo = FrameInfo() // By default, all values should be zero - assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(0) - assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(0) + // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0) // The values inside FrameInfo should match those from ViewFrameInfo after we update them mViewFrameInfo.populateFrameInfo(frameInfo) - assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(10) - assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(20) assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo( FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED) assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted) diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 95044dc5e7bb..6e1cef496f40 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -105,6 +105,7 @@ apex { key: "com.android.apex.apkrollback.test.key", apps: ["TestAppAv1"], installable: false, + updatable: false, } apex { @@ -115,6 +116,7 @@ apex { key: "com.android.apex.apkrollback.test.key", apps: ["TestAppAv2"], installable: false, + updatable: false, } apex { @@ -125,4 +127,5 @@ apex { key: "com.android.apex.apkrollback.test.key", apps: ["TestAppACrashingV2"], installable: false, + updatable: false, } diff --git a/tests/SilkFX/Android.bp b/tests/SilkFX/Android.bp index 92e3efa7fd55..088d9a2d7f41 100644 --- a/tests/SilkFX/Android.bp +++ b/tests/SilkFX/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "SilkFX", srcs: ["**/*.java", "**/*.kt"], diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp index 48031de15f54..dc75f00e7cdc 100644 --- a/tests/SurfaceViewBufferTests/Android.bp +++ b/tests/SurfaceViewBufferTests/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "SurfaceViewBufferTests", srcs: ["**/*.java","**/*.kt"], diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index 43a5078c3c24..ee24d48f0ed5 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_test_host { name: "UpdatableSystemFontTest", srcs: ["src/**/*.java"], diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp index 1296699e3c9f..f744d5dd2b51 100644 --- a/tests/UpdatableSystemFontTest/testdata/Android.bp +++ b/tests/UpdatableSystemFontTest/testdata/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "UpdatableSystemFontTestKeyPem", srcs: ["UpdatableSystemFontTestKey.pem"], diff --git a/tests/benchmarks/internal/Android.bp b/tests/benchmarks/internal/Android.bp index 9c34eaf2af01..74ed7a34f626 100644 --- a/tests/benchmarks/internal/Android.bp +++ b/tests/benchmarks/internal/Android.bp @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + android_test { name: "InternalBenchTests", srcs: ["src/**/*.java"], @@ -23,4 +32,3 @@ android_test { platform_apis: true, certificate: "platform" } - diff --git a/tests/net/common/java/android/net/NetworkStackTest.java b/tests/net/common/java/android/net/NetworkStackTest.java index a99aa0106655..f8f9c72374ad 100644 --- a/tests/net/common/java/android/net/NetworkStackTest.java +++ b/tests/net/common/java/android/net/NetworkStackTest.java @@ -15,20 +15,8 @@ */ package android.net; -import static android.Manifest.permission.NETWORK_STACK; -import static android.content.pm.PackageManager.PERMISSION_DENIED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.net.NetworkStack.checkNetworkStackPermission; -import static android.net.NetworkStack.checkNetworkStackPermissionOr; - import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.when; -import android.content.Context; import android.os.Build; import android.os.IBinder; @@ -46,44 +34,15 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class NetworkStackTest { - private static final String [] OTHER_PERMISSION = {"otherpermission1", "otherpermission2"}; - @Rule public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); - @Mock Context mCtx; @Mock private IBinder mConnectorBinder; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } - @Test - public void testCheckNetworkStackPermission() throws Exception { - when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_GRANTED); - when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK))) - .thenReturn(PERMISSION_DENIED); - checkNetworkStackPermission(mCtx); - checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); - - when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_DENIED); - when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK))) - .thenReturn(PERMISSION_GRANTED); - checkNetworkStackPermission(mCtx); - checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); - - when(mCtx.checkCallingOrSelfPermission(any())).thenReturn(PERMISSION_DENIED); - - try { - checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); - } catch (SecurityException e) { - // Expect to get a SecurityException - return; - } - - fail("Expect fail but permission granted."); - } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testGetService() { NetworkStack.setServiceForTest(mConnectorBinder); diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index 083c8c8741da..9ed55f098a16 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -38,6 +38,7 @@ import android.net.metrics.IpConnectivityLog import android.os.ConditionVariable import android.os.IBinder import android.os.INetworkManagementService +import android.os.SystemConfigManager import android.os.UserHandle import android.testing.TestableContext import android.util.Log @@ -57,6 +58,7 @@ import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import org.mockito.AdditionalAnswers +import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt @@ -94,6 +96,8 @@ class ConnectivityServiceIntegrationTest { private lateinit var netd: INetd @Mock private lateinit var dnsResolver: IDnsResolver + @Mock + private lateinit var systemConfigManager: SystemConfigManager @Spy private var context = TestableContext(realContext) @@ -151,6 +155,11 @@ class ConnectivityServiceIntegrationTest { doReturn(UserHandle.ALL).`when`(asUserCtx).user doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt()) doNothing().`when`(context).sendStickyBroadcast(any(), any()) + doReturn(Context.SYSTEM_CONFIG_SERVICE).`when`(context) + .getSystemServiceName(SystemConfigManager::class.java) + doReturn(systemConfigManager).`when`(context) + .getSystemService(Context.SYSTEM_CONFIG_SERVICE) + doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString()) networkStackClient = TestNetworkStackClient(realContext) networkStackClient.init() diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index dc9e587332cb..e1da3d0ae2b3 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -17,6 +17,7 @@ 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; @@ -84,6 +85,7 @@ 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/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt new file mode 100644 index 000000000000..9b0cfa9db30f --- /dev/null +++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util + +import android.content.Context +import android.content.res.Resources +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY +import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdChangedListener +import android.provider.Settings +import android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI +import android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.test.mock.MockContentResolver +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.internal.R +import com.android.internal.util.test.FakeSettingsProvider +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.argThat +import org.mockito.Mockito.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +/** + * Tests for [MultinetworkPolicyTracker]. + * + * Build, install and run with: + * atest android.net.util.MultinetworkPolicyTrackerTest + */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class MultinetworkPolicyTrackerTest { + private val resources = mock(Resources::class.java).also { + doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi) + } + private val telephonyManager = mock(TelephonyManager::class.java) + private val subscriptionManager = mock(SubscriptionManager::class.java).also { + doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt()) + } + private val resolver = MockContentResolver().apply { + addProvider(Settings.AUTHORITY, FakeSettingsProvider()) } + private val context = mock(Context::class.java).also { + doReturn(Context.TELEPHONY_SERVICE).`when`(it) + .getSystemServiceName(TelephonyManager::class.java) + doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE) + doReturn(subscriptionManager).`when`(it) + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) + doReturn(resolver).`when`(it).contentResolver + doReturn(resources).`when`(it).resources + doReturn(it).`when`(it).createConfigurationContext(any()) + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1") + } + private val tracker = MultinetworkPolicyTracker(context, null /* handler */) + + private fun assertMultipathPreference(preference: Int) { + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + preference.toString()) + tracker.updateMeteredMultipathPreference() + assertEquals(preference, tracker.meteredMultipathPreference) + } + + @Test + fun testUpdateMeteredMultipathPreference() { + assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER) + assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY) + assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE) + } + + @Test + fun testUpdateAvoidBadWifi() { + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + assertTrue(tracker.updateAvoidBadWifi()) + assertFalse(tracker.avoidBadWifi) + + doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi) + assertTrue(tracker.updateAvoidBadWifi()) + assertTrue(tracker.avoidBadWifi) + } + + @Test + fun testOnActiveDataSubscriptionIdChanged() { + val testSubId = 1000 + val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */, + "TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */, + "123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */, + ""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */, + "1"/* cardString */) + doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId) + + // Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in + // MultinetworkPolicyTracker should be also updated after subId changed. + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + MULTIPATH_PREFERENCE_PERFORMANCE.toString()) + + val listenerCaptor = ArgumentCaptor.forClass( + ActiveDataSubscriptionIdChangedListener::class.java) + verify(telephonyManager, times(1)) + .registerPhoneStateListener(any(), listenerCaptor.capture()) + val listener = listenerCaptor.value + listener.onActiveDataSubscriptionIdChanged(testSubId) + + // Check it get resource value with test sub id. + verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId) + verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 }) + + // Check if avoidBadWifi and meteredMultipathPreference values have been updated. + assertFalse(tracker.avoidBadWifi) + assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference) + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 24e559225027..ad87567843c7 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -53,6 +53,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; @@ -238,6 +239,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; +import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -405,6 +407,8 @@ public class ConnectivityServiceTest { private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; private VpnManagerService mVpnManagerService; + private TestNetworkCallback mDefaultNetworkCallback; + private TestNetworkCallback mSystemDefaultNetworkCallback; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -430,6 +434,7 @@ public class ConnectivityServiceTest { @Mock EthernetManager mEthernetManager; @Mock NetworkPolicyManager mNetworkPolicyManager; @Mock KeyStore mKeyStore; + @Mock SystemConfigManager mSystemConfigManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -526,6 +531,7 @@ public class ConnectivityServiceTest { if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; + if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager; return super.getSystemService(name); } @@ -1432,6 +1438,7 @@ public class ConnectivityServiceTest { applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) .thenReturn(applicationInfo); + when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. // http://b/25897652 . @@ -1542,6 +1549,7 @@ public class ConnectivityServiceTest { @After public void tearDown() throws Exception { + unregisterDefaultNetworkCallbacks(); setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); @@ -2783,7 +2791,8 @@ public class ConnectivityServiceTest { if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN || capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA || capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS || - capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP) { + capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP + || capability == NET_CAPABILITY_ENTERPRISE) { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); } else { assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); @@ -2791,6 +2800,10 @@ 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(), @@ -2882,6 +2895,7 @@ public class ConnectivityServiceTest { tryNetworkFactoryRequests(NET_CAPABILITY_IA); tryNetworkFactoryRequests(NET_CAPABILITY_RCS); tryNetworkFactoryRequests(NET_CAPABILITY_XCAP); + tryNetworkFactoryRequests(NET_CAPABILITY_ENTERPRISE); tryNetworkFactoryRequests(NET_CAPABILITY_EIMS); tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED); tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET); @@ -4137,6 +4151,7 @@ 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, mCsHandlerThread); @@ -6041,6 +6056,7 @@ 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) @@ -6049,6 +6065,7 @@ 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 */); @@ -6847,7 +6864,7 @@ public class ConnectivityServiceTest { callback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.getUids().size() == 2 && caps.getUids().contains(new UidRange(uid, uid)) - && caps.getUids().contains(UidRange.createForUser(RESTRICTED_USER)) + && caps.getUids().contains(createUidRange(RESTRICTED_USER)) && caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_WIFI)); @@ -6857,7 +6874,7 @@ public class ConnectivityServiceTest { callback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.getUids().size() == 2 && caps.getUids().contains(new UidRange(uid, uid)) - && caps.getUids().contains(UidRange.createForUser(RESTRICTED_USER)) + && caps.getUids().contains(createUidRange(RESTRICTED_USER)) && caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_WIFI)); @@ -7491,7 +7508,7 @@ public class ConnectivityServiceTest { 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)); + final Set<UidRange> ranges = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.registerAgent(ranges); mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); mMockVpn.connect(true); @@ -7741,19 +7758,13 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.removeCapability(testCap); callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); - // 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); - } + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); mCellNetworkAgent.removeCapability(testCap); callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); callbackWithoutCap.assertNoCallback(); - if (testCap == NET_CAPABILITY_TRUSTED) { - verify(mMockNetd).networkClearDefault(); - } + verify(mMockNetd).networkClearDefault(); mCm.unregisterNetworkCallback(callbackWithCap); mCm.unregisterNetworkCallback(callbackWithoutCap); @@ -8410,7 +8421,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. - final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + final Set<UidRange> vpnRange = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.establish(lp, VPN_UID, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); @@ -8438,7 +8449,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. - final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + final Set<UidRange> vpnRange = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); @@ -8454,7 +8465,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0")); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. - final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + final Set<UidRange> vpnRange = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); @@ -8469,7 +8480,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. - final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + final Set<UidRange> vpnRange = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.establish(lp, VPN_UID, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); @@ -8521,7 +8532,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. - final UidRange vpnRange = UidRange.createForUser(PRIMARY_USER); + final UidRange vpnRange = createUidRange(PRIMARY_USER); final Set<UidRange> vpnRanges = Collections.singleton(vpnRange); mMockVpn.establish(lp, VPN_UID, vpnRanges); assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); @@ -8720,7 +8731,7 @@ public class ConnectivityServiceTest { private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) throws Exception { - final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + final Set<UidRange> vpnRange = Collections.singleton(createUidRange(PRIMARY_USER)); mMockVpn.setVpnType(vpnType); mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); @@ -9279,7 +9290,7 @@ public class ConnectivityServiceTest { lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); - final UidRange vpnRange = UidRange.createForUser(PRIMARY_USER); + final UidRange vpnRange = createUidRange(PRIMARY_USER); Set<UidRange> vpnRanges = Collections.singleton(vpnRange); mMockVpn.establish(lp, VPN_UID, vpnRanges); assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); @@ -9459,6 +9470,10 @@ public class ConnectivityServiceTest { fail("TOO_MANY_REQUESTS never thrown"); } + private UidRange createUidRange(int userId) { + return UidRange.createForUser(UserHandle.of(userId)); + } + private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -9793,6 +9808,54 @@ public class ConnectivityServiceTest { assertEquals(expectedPerAppNetwork, defaultNetwork); assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size()); } + verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork); + } + + /** + * Verify default callbacks for 'available' fire as expected. This will only run if + * registerDefaultNetworkCallbacks() was executed prior and will only be different if the + * setOemNetworkPreference() per-app API was used for the current process. + * @param expectedSystemDefault the expected network for the system default. + * @param expectedPerAppDefault the expected network for the current process's default. + */ + private void verifyMultipleDefaultCallbacks( + @NonNull final Network expectedSystemDefault, + @NonNull final Network expectedPerAppDefault) { + if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault + && mService.mNoServiceNetwork.network() != expectedSystemDefault) { + // getLastAvailableNetwork() is used as this method can be called successively with + // the same network to validate therefore expectAvailableThenValidatedCallbacks + // can't be used. + assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(), + expectedSystemDefault); + } + if (null != mDefaultNetworkCallback && null != expectedPerAppDefault + && mService.mNoServiceNetwork.network() != expectedPerAppDefault) { + assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(), + expectedPerAppDefault); + } + } + + private void registerDefaultNetworkCallbacks() { + // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback() + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + mSystemDefaultNetworkCallback = new TestNetworkCallback(); + mDefaultNetworkCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); + } + + private void unregisterDefaultNetworkCallbacks() { + if (null != mDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + } + if (null != mSystemDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -9876,6 +9939,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9890,6 +9954,7 @@ public class ConnectivityServiceTest { // Verify that the active network is correct verifyActiveNetwork(TRANSPORT_ETHERNET); + // default NCs will be unregistered in tearDown } @Test @@ -9897,6 +9962,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9917,6 +9983,7 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork()); assertFalse(mCm.isActiveNetworkMetered()); + // default NCs will be unregistered in tearDown } @Test @@ -10073,7 +10140,6 @@ public class ConnectivityServiceTest { /** * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). - * @throws Exception */ @Test public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception { @@ -10101,9 +10167,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order: + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly() @@ -10169,9 +10234,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly() @@ -10232,10 +10296,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: * NET_CAPABILITY_OEM_PAID * This preference should only apply to OEM_PAID networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly() @@ -10286,10 +10349,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: * NET_CAPABILITY_OEM_PRIVATE * This preference should only apply to OEM_PRIVATE networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly() @@ -10338,4 +10400,236 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, true /* shouldDestroyNetwork */); } + + /** + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 3; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mCellNetworkAgent.getNetwork()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will put both on null as it is the last network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + null); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 2; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network but not the pref. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: + * NET_CAPABILITY_OEM_PAID + * This preference should only apply to OEM_PAID networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID. + // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and the pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: + * NET_CAPABILITY_OEM_PRIVATE + * This preference should only apply to OEM_PRIVATE networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. + startOemManagedNetwork(false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PRIVATE will keep the fallback on cellular. + // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network. + stopOemManagedNetwork(); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } } diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index c86224a71978..32c95f149979 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -16,12 +16,16 @@ package com.android.server; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -36,6 +40,7 @@ import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.INetd; import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; import android.net.IpSecAlgorithm; import android.net.IpSecConfig; import android.net.IpSecManager; @@ -48,7 +53,6 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.os.Binder; -import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.system.Os; import android.test.mock.MockContext; @@ -148,10 +152,17 @@ public class IpSecServiceParameterizedTest { } throw new SecurityException("Unavailable permission requested"); } + + @Override + public int checkCallingOrSelfPermission(String permission) { + if (android.Manifest.permission.NETWORK_STACK.equals(permission)) { + return PERMISSION_GRANTED; + } + throw new UnsupportedOperationException(); + } }; INetd mMockNetd; - INetworkManagementService mNetworkManager; PackageManager mMockPkgMgr; IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; IpSecService mIpSecService; @@ -181,10 +192,9 @@ public class IpSecServiceParameterizedTest { @Before public void setUp() throws Exception { mMockNetd = mock(INetd.class); - mNetworkManager = mock(INetworkManagementService.class); mMockPkgMgr = mock(PackageManager.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService(mMockContext, mNetworkManager, mMockIpSecSrvConfig); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); // Injecting mock netd when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); @@ -644,7 +654,10 @@ public class IpSecServiceParameterizedTest { } private IpSecTunnelInterfaceResponse createAndValidateTunnel( - String localAddr, String remoteAddr, String pkgName) { + String localAddr, String remoteAddr, String pkgName) throws Exception { + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = new String[] {IF_STATE_DOWN}; + when(mMockNetd.interfaceGetCfg(anyString())).thenReturn(config); IpSecTunnelInterfaceResponse createTunnelResp = mIpSecService.createTunnelInterface( mSourceAddr, mDestinationAddr, fakeNetwork, new Binder(), pkgName); @@ -674,7 +687,8 @@ public class IpSecServiceParameterizedTest { anyInt(), anyInt(), anyInt()); - verify(mNetworkManager).setInterfaceUp(createTunnelResp.interfaceName); + verify(mMockNetd).interfaceSetCfg(argThat( + config -> Arrays.asList(config.flags).contains(IF_STATE_UP))); } @Test diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java index 788e4efe097e..22a2c94fc194 100644 --- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Binder; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -62,8 +61,7 @@ public class IpSecServiceRefcountedResourceTest { public void setUp() throws Exception { mMockContext = mock(Context.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService( - mMockContext, mock(INetworkManagementService.class), mMockIpSecSrvConfig); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); } private void assertResourceState( diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 536e98327e1f..f97eabf6366d 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -42,7 +42,6 @@ import android.net.IpSecManager; import android.net.IpSecSpiResponse; import android.net.IpSecUdpEncapResponse; import android.os.Binder; -import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.os.Process; import android.system.ErrnoException; @@ -116,7 +115,6 @@ public class IpSecServiceTest { } Context mMockContext; - INetworkManagementService mMockNetworkManager; INetd mMockNetd; IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; IpSecService mIpSecService; @@ -124,10 +122,9 @@ public class IpSecServiceTest { @Before public void setUp() throws Exception { mMockContext = mock(Context.class); - mMockNetworkManager = mock(INetworkManagementService.class); mMockNetd = mock(INetd.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService(mMockContext, mMockNetworkManager, mMockIpSecSrvConfig); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); // Injecting mock netd when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); @@ -135,7 +132,7 @@ public class IpSecServiceTest { @Test public void testIpSecServiceCreate() throws InterruptedException { - IpSecService ipSecSrv = IpSecService.create(mMockContext, mMockNetworkManager); + IpSecService ipSecSrv = IpSecService.create(mMockContext); assertNotNull(ipSecSrv); } @@ -608,7 +605,7 @@ public class IpSecServiceTest { public void testOpenUdpEncapSocketTagsSocket() throws Exception { IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class); IpSecService testIpSecService = new IpSecService( - mMockContext, mMockNetworkManager, mMockIpSecSrvConfig, mockTagger); + mMockContext, mMockIpSecSrvConfig, mockTagger); IpSecUdpEncapResponse udpEncapResp = testIpSecService.openUdpEncapsulationSocket(0, new Binder()); diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 8f5ae97bc4c5..e4e24b464838 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -61,6 +61,7 @@ import android.content.pm.PackageManagerInternal; import android.net.INetd; import android.net.UidRange; import android.os.Build; +import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; import android.util.SparseIntArray; @@ -114,6 +115,7 @@ public class PermissionMonitorTest { @Mock private PackageManagerInternal mMockPmi; @Mock private UserManager mUserManager; @Mock private PermissionMonitor.Dependencies mDeps; + @Mock private SystemConfigManager mSystemConfigManager; private PermissionMonitor mPermissionMonitor; @@ -124,6 +126,11 @@ public class PermissionMonitorTest { when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mUserManager.getUserHandles(eq(true))).thenReturn( Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 })); + when(mContext.getSystemServiceName(SystemConfigManager.class)) + .thenReturn(Context.SYSTEM_CONFIG_SERVICE); + when(mContext.getSystemService(Context.SYSTEM_CONFIG_SERVICE)) + .thenReturn(mSystemConfigManager); + when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); @@ -747,4 +754,20 @@ public class PermissionMonitorTest { GET_PERMISSIONS | MATCH_ANY_USER); assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); } + + @Test + public void testUpdateUidPermissionsFromSystemConfig() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(new ArrayList<>()); + when(mSystemConfigManager.getSystemPermissionUids(eq(INTERNET))) + .thenReturn(new int[]{ MOCK_UID1, MOCK_UID2 }); + when(mSystemConfigManager.getSystemPermissionUids(eq(UPDATE_DEVICE_STATS))) + .thenReturn(new int[]{ MOCK_UID2 }); + + mPermissionMonitor.startMonitoring(); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{ MOCK_UID1 }); + mNetdServiceMonitor.expectPermission( + INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS, + new int[]{ MOCK_UID2 }); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index cffd2d1d428f..7489a0f889dc 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -21,6 +21,8 @@ import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_RESTRICTED; import static android.net.ConnectivityManager.NetworkCallback; +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -62,6 +64,7 @@ import android.net.ConnectivityManager; import android.net.INetd; import android.net.Ikev2VpnProfile; import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.IpSecTunnelInterfaceResponse; @@ -179,7 +182,8 @@ public class VpnTest { mPackages.put(PKGS[i], PKG_UIDS[i]); } } - private static final UidRange PRI_USER_RANGE = UidRange.createForUser(primaryUser.id); + private static final UidRange PRI_USER_RANGE = + UidRange.createForUser(UserHandle.of(primaryUser.id)); @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock private UserManager mUserManager; @@ -269,7 +273,7 @@ public class VpnTest { vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, null, null); assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { - PRI_USER_RANGE, UidRange.createForUser(restrictedProfileA.id) + PRI_USER_RANGE, UidRange.createForUser(UserHandle.of(restrictedProfileA.id)) })), ranges); } @@ -872,17 +876,28 @@ public class VpnTest { eq(AppOpsManager.MODE_IGNORED)); } - private NetworkCallback triggerOnAvailableAndGetCallback() { + private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception { final ArgumentCaptor<NetworkCallback> networkCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) .requestNetwork(any(), networkCallbackCaptor.capture()); + // onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be + // invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException. + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = new String[] {IF_STATE_DOWN}; + when(mNetd.interfaceGetCfg(anyString())).thenReturn(config); final NetworkCallback cb = networkCallbackCaptor.getValue(); cb.onAvailable(TEST_NETWORK); return cb; } + private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception { + // Add a timeout for waiting for interfaceSetCfg to be called. + verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat( + config -> Arrays.asList(config.flags).contains(flag))); + } + @Test public void testStartPlatformVpnAuthenticationFailed() throws Exception { final ArgumentCaptor<IkeSessionCallback> captor = @@ -894,6 +909,8 @@ public class VpnTest { final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile)); final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + verifyInterfaceSetCfgWithFlags(IF_STATE_UP); + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent // state verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)) @@ -912,6 +929,8 @@ public class VpnTest { final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), mVpnProfile); final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + verifyInterfaceSetCfgWithFlags(IF_STATE_UP); + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent // state verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 708767605508..66590c92579b 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -16,6 +16,8 @@ package android.net.vcn; +import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; + import static androidx.test.InstrumentationRegistry.getContext; import static org.junit.Assert.assertEquals; @@ -204,10 +206,13 @@ public class VcnManagerTest { cbBinder.onEnteredSafeMode(); verify(mMockStatusCallback).onEnteredSafeMode(); + cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); + verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); + cbBinder.onGatewayConnectionError( UNDERLYING_NETWORK_CAPABILITIES, VcnManager.VCN_ERROR_CODE_NETWORK_ERROR, - "java.net.UnknownHostException", + UnknownHostException.class.getName(), "exception_message"); verify(mMockStatusCallback) .onGatewayConnectionError( diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java index 3ba0a1f53a9f..a674425efea3 100644 --- a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java +++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkPolicyTest.java @@ -46,6 +46,6 @@ public class VcnUnderlyingNetworkPolicyTest { @Test public void testParcelUnparcel() { - assertParcelSane(SAMPLE_NETWORK_POLICY, 2); + assertParcelSane(SAMPLE_NETWORK_POLICY, 1); } } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 45b2381ce06d..9b500a7271d7 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -43,7 +43,6 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; 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.when; @@ -59,6 +58,7 @@ import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; +import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkPolicy; import android.net.wifi.WifiInfo; import android.os.IBinder; @@ -783,7 +783,7 @@ public class VcnManagementServiceTest { true /* hasPermissionsforSubGroup */, true /* hasLocationPermission */); - verify(mMockStatusCallback, times(1)).onEnteredSafeMode(); + verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test @@ -795,7 +795,8 @@ public class VcnManagementServiceTest { false /* hasPermissionsforSubGroup */, true /* hasLocationPermission */); - verify(mMockStatusCallback, never()).onEnteredSafeMode(); + verify(mMockStatusCallback, never()) + .onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test @@ -807,7 +808,8 @@ public class VcnManagementServiceTest { true /* hasPermissionsforSubGroup */, false /* hasLocationPermission */); - verify(mMockStatusCallback, never()).onEnteredSafeMode(); + verify(mMockStatusCallback, never()) + .onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index f0b759547a93..ebc0ec1640f5 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -11,6 +11,12 @@ from fontTools import ttLib EMOJI_VS = 0xFE0F +#TODO(179952916): Rename CutiveMono and DancingScript +CANONICAL_NAME_EXCEPTION_LIST = [ + 'CutiveMono.ttf', + 'DancingScript-Regular.ttf', +] + LANG_TO_SCRIPT = { 'as': 'Beng', 'be': 'Cyrl', @@ -665,6 +671,53 @@ def check_cjk_punctuation(): assert_font_supports_none_of_chars(record.font, cjk_punctuation, name) +def getPostScriptName(font): + ttf = open_font(font) + nameTable = ttf['name'] + for name in nameTable.names: + if name.nameID == 6 and name.platformID == 3 and name.platEncID == 1 and name.langID == 0x0409: + return str(name) + + +def getSuffix(font): + file_path, index = font + with open(path.join(_fonts_dir, file_path), 'rb') as f: + tag = f.read(4) + isCollection = tag == b'ttcf' + + ttf = open_font(font) + isType1 = ('CFF ' in ttf or 'CFF2' in ttf) + + if isType1: + if isCollection: + return '.otc' + else: + return '.otf' + else: + if isCollection: + return '.ttc' + else: + return '.ttf' + + +def check_canonical_name(): + for record in _all_fonts: + file_name, index = record.font + if file_name in CANONICAL_NAME_EXCEPTION_LIST: + continue + + if index and index != 0: + continue + + psName = getPostScriptName(record.font) + assert psName, 'PostScript must be defined' + + suffix = getSuffix(record.font) + canonicalName = '%s%s' % (psName, suffix) + + assert file_name == canonicalName, ( + '%s is not a canonical name. Must be %s' % (file_name, canonicalName)) + def main(): global _fonts_dir target_out = sys.argv[1] @@ -682,6 +735,8 @@ def main(): check_cjk_punctuation() + check_canonical_name() + check_emoji = sys.argv[2] if check_emoji == 'true': ucd_path = sys.argv[3] diff --git a/tools/powerstats/Android.bp b/tools/powerstats/Android.bp index af41144167a9..9c58daf0f922 100644 --- a/tools/powerstats/Android.bp +++ b/tools/powerstats/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_binary_host { name: "PowerStatsServiceProtoParser", manifest: "PowerStatsServiceProtoParser_manifest.txt", diff --git a/tools/processors/intdef_mappings/Android.bp b/tools/processors/intdef_mappings/Android.bp index e255f7c784d3..82a5dac21160 100644 --- a/tools/processors/intdef_mappings/Android.bp +++ b/tools/processors/intdef_mappings/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_plugin { name: "intdef-annotation-processor", @@ -30,4 +39,4 @@ java_test_host { ], test_suites: ["general-tests"], -}
\ No newline at end of file +} diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp index d58d0dcdc45a..0b6dba626794 100644 --- a/tools/xmlpersistence/Android.bp +++ b/tools/xmlpersistence/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + java_binary_host { name: "xmlpersistence_cli", manifest: "manifest.txt", |