diff options
608 files changed, 18934 insertions, 5605 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 3ff2546223de..665deda8aed1 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -123,6 +123,20 @@ droidstubs { new_since: ":android-non-updatable.api.public.latest", }, }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/public/api", + dest: "android-non-updatable.txt", + tag: ".api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/public/api", + dest: "android-non-updatable-removed.txt", + tag: ".removed-api.txt", + }, + ], } priv_apps = @@ -162,6 +176,20 @@ droidstubs { baseline_file: "core/api/system-lint-baseline.txt", }, }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system/api", + dest: "android-non-updatable.txt", + tag: ".api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system/api", + dest: "android-non-updatable-removed.txt", + tag: ".removed-api.txt", + }, + ], } droidstubs { @@ -178,11 +206,32 @@ droidstubs { baseline_file: "core/api/test-lint-baseline.txt", }, }, - dist: { - targets: ["sdk", "win_sdk"], - dir: "apistubs/android/test/api", - dest: "android.txt", - }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/test/api", + dest: "android.txt", + tag: ".api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/test/api", + dest: "removed.txt", + tag: ".removed-api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/test/api", + dest: "android-non-updatable.txt", + tag: ".api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/test/api", + dest: "android-non-updatable-removed.txt", + tag: ".removed-api.txt", + }, + ], } droidstubs { @@ -203,6 +252,20 @@ droidstubs { new_since: ":android-non-updatable.api.module-lib.latest", }, }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/module-lib/api", + dest: "android-non-updatable.txt", + tag: ".api.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/module-lib/api", + dest: "android-non-updatable-removed.txt", + tag: ".removed-api.txt", + }, + ], } ///////////////////////////////////////////////////////////////////// diff --git a/apex/OWNERS b/apex/OWNERS index 97600135a103..bde2bec0816b 100644 --- a/apex/OWNERS +++ b/apex/OWNERS @@ -1,7 +1,8 @@ -# Shared module build rule owners -per-file *.bp=hansson@google.com -per-file *.bp=jiyong@google.com +# Mainline modularization team -# This file, and all other OWNERS files -per-file OWNERS=dariofreni@google.com -per-file OWNERS=hansson@google.com +andreionea@google.com +dariofreni@google.com +hansson@google.com +mathewi@google.com +pedroql@google.com +satayev@google.com diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index 2320b75247a0..b1394c1441f0 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -22,6 +22,14 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String); } + public interface AppSearchMigrationHelper { + method public void queryAndTransform(@NonNull String, @NonNull android.app.appsearch.AppSearchMigrationHelper.Transformer) throws java.lang.Exception; + } + + public static interface AppSearchMigrationHelper.Transformer { + method @NonNull public android.app.appsearch.GenericDocument transform(int, int, @NonNull android.app.appsearch.GenericDocument) throws java.lang.Exception; + } + public final class AppSearchResult<ValueType> { method @Nullable public String getErrorMessage(); method public int getResultCode(); @@ -40,30 +48,82 @@ package android.app.appsearch { public final class AppSearchSchema { method @NonNull public java.util.List<android.app.appsearch.AppSearchSchema.PropertyConfig> getProperties(); method @NonNull public String getSchemaType(); + method @IntRange(from=0) public int getVersion(); + } + + public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + } + + public static final class AppSearchSchema.BooleanPropertyConfig.Builder { + ctor public AppSearchSchema.BooleanPropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.BooleanPropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.BooleanPropertyConfig.Builder setCardinality(int); } public static final class AppSearchSchema.Builder { ctor public AppSearchSchema.Builder(@NonNull String); method @NonNull public android.app.appsearch.AppSearchSchema.Builder addProperty(@NonNull android.app.appsearch.AppSearchSchema.PropertyConfig); method @NonNull public android.app.appsearch.AppSearchSchema build(); + method @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int); + } + + public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + } + + public static final class AppSearchSchema.BytesPropertyConfig.Builder { + ctor public AppSearchSchema.BytesPropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.BytesPropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.BytesPropertyConfig.Builder setCardinality(int); + } + + public static final class AppSearchSchema.DocumentPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + method @NonNull public String getSchemaType(); + method public boolean isIndexNestedProperties(); + } + + public static final class AppSearchSchema.DocumentPropertyConfig.Builder { + ctor public AppSearchSchema.DocumentPropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder setCardinality(int); + method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder setIndexNestedProperties(boolean); + method @NonNull public android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder setSchemaType(@NonNull String); + } + + public static final class AppSearchSchema.DoublePropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + } + + public static final class AppSearchSchema.DoublePropertyConfig.Builder { + ctor public AppSearchSchema.DoublePropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.DoublePropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.DoublePropertyConfig.Builder setCardinality(int); + } + + public static final class AppSearchSchema.Int64PropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + } + + public static final class AppSearchSchema.Int64PropertyConfig.Builder { + ctor public AppSearchSchema.Int64PropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.Int64PropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.Int64PropertyConfig.Builder setCardinality(int); } - public static final class AppSearchSchema.PropertyConfig { + public static interface AppSearchSchema.Migrator { + method public default void onDowngrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception; + method public default void onUpgrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception; + } + + public abstract static class AppSearchSchema.PropertyConfig { method public int getCardinality(); method public int getDataType(); - method public int getIndexingType(); method @NonNull public String getName(); - method @Nullable public String getSchemaType(); - method public int getTokenizerType(); field public static final int CARDINALITY_OPTIONAL = 2; // 0x2 field public static final int CARDINALITY_REPEATED = 1; // 0x1 field public static final int CARDINALITY_REQUIRED = 3; // 0x3 - field public static final int DATA_TYPE_BOOLEAN = 4; // 0x4 - field public static final int DATA_TYPE_BYTES = 5; // 0x5 - field public static final int DATA_TYPE_DOCUMENT = 6; // 0x6 - field public static final int DATA_TYPE_DOUBLE = 3; // 0x3 - field public static final int DATA_TYPE_INT64 = 2; // 0x2 - field public static final int DATA_TYPE_STRING = 1; // 0x1 + } + + public static final class AppSearchSchema.StringPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { + method public int getIndexingType(); + method public int getTokenizerType(); field public static final int INDEXING_TYPE_EXACT_TERMS = 1; // 0x1 field public static final int INDEXING_TYPE_NONE = 0; // 0x0 field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2 @@ -71,14 +131,12 @@ package android.app.appsearch { field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1 } - public static final class AppSearchSchema.PropertyConfig.Builder { - ctor public AppSearchSchema.PropertyConfig.Builder(@NonNull String); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig build(); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig.Builder setCardinality(int); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig.Builder setDataType(int); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig.Builder setIndexingType(int); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig.Builder setSchemaType(@NonNull String); - method @NonNull public android.app.appsearch.AppSearchSchema.PropertyConfig.Builder setTokenizerType(int); + public static final class AppSearchSchema.StringPropertyConfig.Builder { + ctor public AppSearchSchema.StringPropertyConfig.Builder(@NonNull String); + method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig build(); + method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setCardinality(int); + method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setIndexingType(int); + method @NonNull public android.app.appsearch.AppSearchSchema.StringPropertyConfig.Builder setTokenizerType(int); } public final class AppSearchSession implements java.io.Closeable { @@ -89,7 +147,8 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); method public void removeByQuery(@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 public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); - method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @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 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>>); } public interface BatchResultCallback<KeyType, ValueType> { @@ -140,11 +199,15 @@ package android.app.appsearch { public final class GetByUriRequest { method @NonNull public String getNamespace(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections(); method @NonNull public java.util.Set<java.lang.String> getUris(); + field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; } public static final class GetByUriRequest.Builder { ctor public GetByUriRequest.Builder(); + method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.lang.String...); + method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest build(); @@ -186,7 +249,22 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String); } + public final class ReportUsageRequest { + method @NonNull public String getNamespace(); + method @NonNull public String getUri(); + method public long getUsageTimeMillis(); + } + + public static final class ReportUsageRequest.Builder { + ctor public ReportUsageRequest.Builder(); + method @NonNull public android.app.appsearch.ReportUsageRequest build(); + method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setNamespace(@NonNull String); + method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUri(@NonNull String); + method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUsageTimeMillis(long); + } + public final class SearchResult { + method @NonNull public String getDatabaseName(); method @NonNull public android.app.appsearch.GenericDocument getDocument(); method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches(); method @NonNull public String getPackageName(); @@ -230,6 +308,8 @@ package android.app.appsearch { field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1 field public static final int RANKING_STRATEGY_NONE = 0; // 0x0 field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3 + field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4 + field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5 field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1 field public static final int TERM_MATCH_PREFIX = 2; // 0x2 } @@ -255,6 +335,7 @@ package android.app.appsearch { } public final class SetSchemaRequest { + method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.AppSearchSchema.Migrator> getMigrators(); method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas(); method @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages(); @@ -267,10 +348,26 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>); method @NonNull public android.app.appsearch.SetSchemaRequest build(); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean); + method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean); } + public class SetSchemaResponse { + method @NonNull public java.util.Set<java.lang.String> getDeletedTypes(); + 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 { + method @NonNull public android.app.appsearch.AppSearchResult<java.lang.Void> getAppSearchResult(); + method @NonNull public String getNamespace(); + method @NonNull public String getSchemaType(); + method @NonNull public String getUri(); + } + } package android.app.appsearch.exceptions { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 6fa8f850100b..814800ebe41e 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -46,6 +46,7 @@ import java.util.function.Consumer; public class AppSearchManager { /** * The default empty database name. + * * @hide */ public static final String DEFAULT_DATABASE_NAME = ""; @@ -70,8 +71,8 @@ public class AppSearchManager { /** * Returns the name of the database to create or open. * - * <p>Databases with different names are fully separate with distinct types, namespaces, - * and data. + * <p>Databases with different names are fully separate with distinct types, namespaces, and + * data. */ @NonNull public String getDatabaseName() { @@ -126,11 +127,11 @@ public class AppSearchManager { * initialization process will create one under the user's credential encrypted directory. * * @param searchContext The {@link SearchContext} contains all information to create a new - * {@link AppSearchSession} - * @param executor Executor on which to invoke the callback. - * @param callback The {@link AppSearchResult}<{@link AppSearchSession}> of - * performing this operation. Or a {@link AppSearchResult} with failure - * reason code and error information. + * {@link AppSearchSession} + * @param executor Executor on which to invoke the callback. + * @param callback The {@link AppSearchResult}<{@link AppSearchSession}> of performing + * this operation. Or a {@link AppSearchResult} with failure reason code and error + * information. */ public void createSearchSession( @NonNull SearchContext searchContext, @@ -140,7 +141,12 @@ public class AppSearchManager { Objects.requireNonNull(executor); Objects.requireNonNull(callback); AppSearchSession.createSearchSession( - searchContext, mService, mContext.getUserId(), executor, callback); + searchContext, + mService, + mContext.getUserId(), + getPackageName(), + executor, + callback); } /** @@ -149,10 +155,10 @@ public class AppSearchManager { * <p>This process requires an AppSearch native indexing file system. If it's not created, the * initialization process will create one under the user's credential encrypted directory. * - * @param executor Executor on which to invoke the callback. - * @param callback The {@link AppSearchResult}<{@link GlobalSearchSession}> of - * performing this operation. Or a {@link AppSearchResult} with failure - * reason code and error information. + * @param executor Executor on which to invoke the callback. + * @param callback The {@link AppSearchResult}<{@link GlobalSearchSession}> of performing + * this operation. Or a {@link AppSearchResult} with failure reason code and error + * information. */ public void createGlobalSearchSession( @NonNull @CallbackExecutor Executor executor, @@ -160,7 +166,7 @@ public class AppSearchManager { Objects.requireNonNull(executor); Objects.requireNonNull(callback); GlobalSearchSession.createGlobalSearchSession( - mService, mContext.getUserId(), executor, callback); + mService, mContext.getUserId(), getPackageName(), executor, callback); } /** @@ -170,40 +176,42 @@ public class AppSearchManager { * 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 android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL - * OPTIONAL} or - * {@link android.app.appsearch.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 android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL - * OPTIONAL} property into a - * {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED - * REPEATED} property. + * <li>Addition of new types + * <li>Addition of new {@link + * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or + * {@link android.app.appsearch.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 + * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} + * property into a {@link + * android.app.appsearch.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 GenericDocument} type, changing the schema type of - * {@code GenericDocument}s of that property - * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an - * {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL - * OPTIONAL} property into a - * {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED - * REQUIRED} property). - * <li>Adding a - * {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED - * REQUIRED} property. + * <li>Removal of an existing type + * <li>Removal of a property from a type + * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property + * <li>For properties of {@code GenericDocument} type, changing the schema type of {@code + * GenericDocument}s of that property + * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link + * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} + * property into a {@link + * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} + * property). + * <li>Adding a {@link + * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} + * property. * </ul> - * <p>Supplying a schema with such changes will result in this call returning an - * {@link AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an - * error message describing the incompatibility. In this case the previously set schema will - * remain active. + * + * <p>Supplying a schema with such changes will result in this call returning an {@link + * AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an error + * message describing the incompatibility. In this case the previously set schema will remain + * active. * * <p>If you need to make non-backwards-compatible changes as described above, instead use the * {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to @@ -214,8 +222,8 @@ public class AppSearchManager { * * @param request The schema update request. * @return the result of performing this operation. - * @deprecated use {@link AppSearchSession#setSchema} instead. * @hide + * @deprecated use {@link AppSearchSession#setSchema} instead. */ @NonNull public AppSearchResult<Void> setSchema(@NonNull SetSchemaRequest request) { @@ -229,6 +237,7 @@ public class AppSearchManager { AndroidFuture<AppSearchResult> future = new AndroidFuture<>(); try { mService.setSchema( + getPackageName(), DEFAULT_DATABASE_NAME, schemaBundles, new ArrayList<>(request.getSchemasNotVisibleToSystemUi()), @@ -249,21 +258,19 @@ public class AppSearchManager { /** * Index {@link GenericDocument}s into AppSearch. * - * <p>You should not call this method directly; instead, use the - * {@code AppSearch#putDocuments()} API provided by JetPack. + * <p>You should not call this method directly; instead, use the {@code + * AppSearch#putDocuments()} API provided by JetPack. * * <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a * schema type previously registered via the {@link #setSchema} method. * * @param request {@link PutDocumentsRequest} containing documents to be indexed - * @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. + * @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. * @throws RuntimeException If an error occurred during the execution. - * - * @deprecated use {@link AppSearchSession#putDocuments} instead. * @hide + * @deprecated use {@link AppSearchSession#putDocuments} instead. */ public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) { // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in @@ -275,7 +282,11 @@ public class AppSearchManager { } AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); try { - mService.putDocuments(DEFAULT_DATABASE_NAME, documentBundles, mContext.getUserId(), + mService.putDocuments( + getPackageName(), + DEFAULT_DATABASE_NAME, + documentBundles, + mContext.getUserId(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { future.complete(result); @@ -294,19 +305,18 @@ public class AppSearchManager { /** * Retrieves {@link GenericDocument}s by URI. * - * <p>You should not call this method directly; instead, use the - * {@code AppSearch#getDocuments()} API provided by JetPack. + * <p>You should not call this method directly; instead, use the {@code + * AppSearch#getDocuments()} API provided by JetPack. * * @param request {@link GetByUriRequest} containing URIs to be retrieved. - * @return The pending result of performing this operation. The keys of the returned - * {@link AppSearchBatchResult} are the input URIs. The values are the returned - * {@link GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. - * URIs that are not found will return a failed {@link AppSearchResult} with a result code - * of {@link AppSearchResult#RESULT_NOT_FOUND}. + * @return The pending result of performing this operation. The keys of the returned {@link + * AppSearchBatchResult} are the input URIs. The values are the returned {@link + * GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that + * are not found will return a failed {@link AppSearchResult} with a result code of {@link + * AppSearchResult#RESULT_NOT_FOUND}. * @throws RuntimeException If an error occurred during the execution. - * - * @deprecated use {@link AppSearchSession#getByUri} instead. * @hide + * @deprecated use {@link AppSearchSession#getByUri} instead. */ public AppSearchBatchResult<String, GenericDocument> getByUri( @NonNull GetByUriRequest request) { @@ -315,7 +325,12 @@ public class AppSearchManager { List<String> uris = new ArrayList<>(request.getUris()); AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); try { - mService.getDocuments(DEFAULT_DATABASE_NAME, request.getNamespace(), uris, + mService.getDocuments( + getPackageName(), + DEFAULT_DATABASE_NAME, + request.getNamespace(), + uris, + request.getProjectionsVisibleToPackagesInternal(), mContext.getUserId(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { @@ -371,43 +386,39 @@ public class AppSearchManager { * provided by JetPack. * * <p>Currently we support following features in the raw query format: + * * <ul> - * <li>AND - * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and - * ‘cat’”). - * Example: hello world matches documents that have both ‘hello’ and ‘world’ - * <li>OR - * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or - * ‘cat’”). - * Example: dog OR puppy - * <li>Exclusion - * <p>Exclude a term (e.g. “match documents that do - * not have the term ‘dog’”). - * Example: -dog excludes the term ‘dog’ - * <li>Grouping terms - * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. - * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). - * Example: (dog puppy) (cat kitten) two one group containing two terms. - * <li>Property restricts - * <p> Specifies which properties of a document to specifically match terms in (e.g. - * “match documents where the ‘subject’ property contains ‘important’”). - * Example: subject:important matches documents with the term ‘important’ in the - * ‘subject’ property - * <li>Schema type restricts - * <p>This is similar to property restricts, but allows for restricts on top-level document - * fields, such as schema_type. Clients should be able to limit their query to documents of - * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”). - * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents - * that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the - * ‘Video’ schema type. + * <li>AND + * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”). + * Example: hello world matches documents that have both ‘hello’ and ‘world’ + * <li>OR + * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example: + * dog OR puppy + * <li>Exclusion + * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example: + * -dog excludes the term ‘dog’ + * <li>Grouping terms + * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. + * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). + * Example: (dog puppy) (cat kitten) two one group containing two terms. + * <li>Property restricts + * <p>Specifies which properties of a document to specifically match terms in (e.g. “match + * documents where the ‘subject’ property contains ‘important’”). Example: + * subject:important matches documents with the term ‘important’ in the ‘subject’ property + * <li>Schema type restricts + * <p>This is similar to property restricts, but allows for restricts on top-level + * document fields, such as schema_type. Clients should be able to limit their query to + * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’ + * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will + * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema + * type or the ‘Video’ schema type. * </ul> * * @param queryExpression Query String to search. * @param searchSpec Spec for setting filters, raw query etc. * @throws RuntimeException If an error occurred during the execution. - * - * @deprecated use AppSearchSession#query instead. * @hide + * @deprecated use AppSearchSession#query instead. */ @NonNull public AppSearchResult<List<SearchResult>> query( @@ -416,7 +427,11 @@ public class AppSearchManager { // them in one big list. AndroidFuture<AppSearchResult> future = new AndroidFuture<>(); try { - mService.query(DEFAULT_DATABASE_NAME, queryExpression, searchSpec.getBundle(), + mService.query( + getPackageName(), + DEFAULT_DATABASE_NAME, + queryExpression, + searchSpec.getBundle(), mContext.getUserId(), new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { @@ -425,8 +440,8 @@ public class AppSearchManager { }); AppSearchResult<Bundle> bundleResult = getFutureOrThrow(future); if (!bundleResult.isSuccess()) { - return AppSearchResult.newFailedResult(bundleResult.getResultCode(), - bundleResult.getErrorMessage()); + return AppSearchResult.newFailedResult( + bundleResult.getResultCode(), bundleResult.getErrorMessage()); } SearchResultPage searchResultPage = new SearchResultPage(bundleResult.getResultValue()); return AppSearchResult.newSuccessfulResult(searchResultPage.getResults()); @@ -444,21 +459,23 @@ public class AppSearchManager { * provided by JetPack. * * @param request Request containing URIs to be removed. - * @return The pending result of performing this operation. The keys of the returned - * {@link AppSearchBatchResult} are the input URIs. The values are {@code null} on success, - * or a failed {@link AppSearchResult} otherwise. URIs that are not found will return a - * failed {@link AppSearchResult} with a result code of - * {@link AppSearchResult#RESULT_NOT_FOUND}. + * @return The pending result of performing this operation. The keys of the returned {@link + * AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a + * failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed + * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}. * @throws RuntimeException If an error occurred during the execution. - * - * @deprecated use {@link AppSearchSession#removeByUri} instead. * @hide + * @deprecated use {@link AppSearchSession#removeByUri} instead. */ public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) { List<String> uris = new ArrayList<>(request.getUris()); AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); try { - mService.removeByUri(DEFAULT_DATABASE_NAME, request.getNamespace(), uris, + mService.removeByUri( + getPackageName(), + DEFAULT_DATABASE_NAME, + request.getNamespace(), + uris, mContext.getUserId(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { @@ -475,6 +492,12 @@ public class AppSearchManager { return getFutureOrThrow(future); } + /** Returns the package name that should be used for uid verification. */ + @NonNull + private String getPackageName() { + return mContext.getOpPackageName(); + } + private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) { try { return future.get(); diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 042757797dce..670f8b9c0236 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -45,6 +45,7 @@ import java.util.function.Consumer; */ public final class AppSearchSession implements Closeable { private static final String TAG = "AppSearchSession"; + private final String mPackageName; private final String mDatabaseName; @UserIdInt private final int mUserId; @@ -52,14 +53,20 @@ public final class AppSearchSession implements Closeable { private boolean mIsMutated = false; private boolean mIsClosed = false; + + /** + * Creates a search session for the client, defined by the {@code userId} and + * {@code packageName}. + */ static void createSearchSession( @NonNull AppSearchManager.SearchContext searchContext, @NonNull IAppSearchManager service, @UserIdInt int userId, + @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) { AppSearchSession searchSession = - new AppSearchSession(service, userId, searchContext.mDatabaseName); + new AppSearchSession(service, userId, packageName, searchContext.mDatabaseName); searchSession.initialize(executor, callback); } @@ -87,10 +94,11 @@ public final class AppSearchSession implements Closeable { } private AppSearchSession(@NonNull IAppSearchManager service, @UserIdInt int userId, - @NonNull String databaseName) { - mDatabaseName = databaseName; + @NonNull String packageName, @NonNull String databaseName) { mService = service; mUserId = userId; + mPackageName = packageName; + mDatabaseName = databaseName; } /** @@ -144,7 +152,7 @@ public final class AppSearchSession implements Closeable { * Visibility settings for a schema type do not apply or persist across * {@link SetSchemaRequest}s. * - * @param request The schema update request. + * @param request The schema update request. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive errors resulting from setting the schema. If the * operation succeeds, the callback will be invoked with {@code null}. @@ -154,7 +162,7 @@ public final class AppSearchSession implements Closeable { public void setSchema( @NonNull SetSchemaRequest request, @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<AppSearchResult<Void>> callback) { + @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -175,6 +183,7 @@ public final class AppSearchSession implements Closeable { } try { mService.setSchema( + mPackageName, mDatabaseName, schemaBundles, new ArrayList<>(request.getSchemasNotVisibleToSystemUi()), @@ -183,7 +192,18 @@ public final class AppSearchSession implements Closeable { mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { - executor.execute(() -> callback.accept(result)); + executor.execute(() -> { + if (result.isSuccess()) { + callback.accept( + // TODO(b/151178558) implement Migration in platform. + AppSearchResult.newSuccessfulResult( + new SetSchemaResponse.Builder().setResultCode( + result.getResultCode()) + .build())); + } else { + callback.accept(result); + } + }); } }); mIsMutated = true; @@ -206,6 +226,7 @@ public final class AppSearchSession implements Closeable { Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); try { mService.getSchema( + mPackageName, mDatabaseName, mUserId, new IAppSearchResultCallback.Stub() { @@ -261,7 +282,7 @@ public final class AppSearchSession implements Closeable { documentBundles.add(documents.get(i).getBundle()); } try { - mService.putDocuments(mDatabaseName, documentBundles, mUserId, + mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId, new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); @@ -280,7 +301,7 @@ public final class AppSearchSession implements Closeable { /** * Retrieves {@link GenericDocument}s by URI. * - * @param request {@link GetByUriRequest} containing URIs to be retrieved. + * @param request {@link GetByUriRequest} containing URIs to be retrieved. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. The keys * of the returned {@link AppSearchBatchResult} are the input URIs. The values @@ -301,8 +322,13 @@ public final class AppSearchSession implements Closeable { Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); try { - mService.getDocuments(mDatabaseName, request.getNamespace(), - new ArrayList<>(request.getUris()), mUserId, + mService.getDocuments( + mPackageName, + mDatabaseName, + request.getNamespace(), + new ArrayList<>(request.getUris()), + request.getProjectionsVisibleToPackagesInternal(), + mUserId, new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { executor.execute(() -> { @@ -405,14 +431,59 @@ public final class AppSearchSession implements Closeable { Objects.requireNonNull(searchSpec); Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); - return new SearchResults(mService, mDatabaseName, queryExpression, searchSpec, mUserId, - executor); + return new SearchResults(mService, mPackageName, mDatabaseName, queryExpression, + searchSpec, mUserId, executor); + } + + /** + * Reports usage of a particular document by URI and namespace. + * + * <p>A usage report represents an event in which a user interacted with or viewed a document. + * + * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency + * metrics for that particular document. These metrics are used for ordering {@link #query} + * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and + * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. + * + * <p>Reporting usage of a document is optional. + * + * @param request The usage reporting request. + * @param executor Executor on which to invoke the callback. + * @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, + @NonNull Consumer<AppSearchResult<Void>> callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); + try { + mService.reportUsage( + mPackageName, + mDatabaseName, + request.getNamespace(), + request.getUri(), + request.getUsageTimeMillis(), + mUserId, + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> callback.accept(result)); + } + }); + mIsMutated = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * Removes {@link GenericDocument}s from the index by URI. * - * @param request Request containing URIs to be removed. + * @param request Request containing URIs to be removed. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. The keys * of the returned {@link AppSearchBatchResult} are the input URIs. The values @@ -432,7 +503,7 @@ public final class AppSearchSession implements Closeable { Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); try { - mService.removeByUri(mDatabaseName, request.getNamespace(), + mService.removeByUri(mPackageName, mDatabaseName, request.getNamespace(), new ArrayList<>(request.getUris()), mUserId, new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { @@ -478,7 +549,8 @@ public final class AppSearchSession implements Closeable { Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); try { - mService.removeByQuery(mDatabaseName, queryExpression, searchSpec.getBundle(), mUserId, + mService.removeByQuery(mPackageName, mDatabaseName, queryExpression, + searchSpec.getBundle(), mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { executor.execute(() -> callback.accept(result)); diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index e4e030e0d18a..6bb855480bc9 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -37,16 +37,25 @@ import java.util.function.Consumer; public class GlobalSearchSession implements Closeable { private final IAppSearchManager mService; + @UserIdInt private final int mUserId; private boolean mIsClosed = false; + private final String mPackageName; + + /** + * Creates a search session for the client, defined by the {@code userId} and + * {@code packageName}. + */ static void createGlobalSearchSession( @NonNull IAppSearchManager service, @UserIdInt int userId, + @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) { - GlobalSearchSession globalSearchSession = new GlobalSearchSession(service, userId); + GlobalSearchSession globalSearchSession = new GlobalSearchSession(service, userId, + packageName); globalSearchSession.initialize(executor, callback); } @@ -73,9 +82,11 @@ public class GlobalSearchSession implements Closeable { } } - private GlobalSearchSession(@NonNull IAppSearchManager service, @UserIdInt int userId) { + private GlobalSearchSession(@NonNull IAppSearchManager service, @UserIdInt int userId, + @NonNull String packageName) { mService = service; mUserId = userId; + mPackageName = packageName; } /** @@ -131,7 +142,7 @@ public class GlobalSearchSession implements Closeable { Objects.requireNonNull(searchSpec); Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed"); - return new SearchResults(mService, /*databaseName=*/null, queryExpression, + return new SearchResults(mService, mPackageName, /*databaseName=*/null, queryExpression, searchSpec, mUserId, executor); } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 2b437774b473..68ae23f9a9db 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -30,6 +30,7 @@ interface IAppSearchManager { /** * Updates the AppSearch schema for this database. * + * @param packageName The name of the package that owns this schema. * @param databaseName The name of the database where this schema lives. * @param schemaBundles List of {@link AppSearchSchema} bundles. * @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform @@ -43,6 +44,7 @@ interface IAppSearchManager { * {@link AppSearchResult}<{@link Void}>. */ void setSchema( + in String packageName, in String databaseName, in List<Bundle> schemaBundles, in List<String> schemasNotPlatformSurfaceable, @@ -54,17 +56,23 @@ interface IAppSearchManager { /** * Retrieves the AppSearch schema for this database. * + * @param packageName The name of the package that owns the schema. * @param databaseName The name of the database to retrieve. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an * {@link AppSearchResult}<{@link List}<{@link Bundle}>>, where the value are * AppSearchSchema bundle. */ - void getSchema(in String databaseName, in int userId, in IAppSearchResultCallback callback); + void getSchema( + in String packageName, + in String databaseName, + in int userId, + in IAppSearchResultCallback callback); /** * Inserts documents into the index. * + * @param packageName The name of the package that owns this document. * @param databaseName The name of the database where this document lives. * @param documentBundes List of GenericDocument bundles. * @param userId Id of the calling user @@ -76,6 +84,7 @@ interface IAppSearchManager { * where the keys are document URIs, and the values are {@code null}. */ void putDocuments( + in String packageName, in String databaseName, in List<Bundle> documentBundles, in int userId, @@ -84,9 +93,12 @@ interface IAppSearchManager { /** * Retrieves documents from the index. * + * @param packageName The name of the package that owns this document. * @param databaseName The databaseName this document resides in. * @param namespace The namespace this document resides in. * @param uris The URIs of the documents to retrieve + * @param typePropertyPaths A map of schema type to a list of property paths to return in the + * result. * @param userId Id of the calling user * @param callback * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError} @@ -96,15 +108,18 @@ interface IAppSearchManager { * where the keys are document URIs, and the values are Document bundles. */ void getDocuments( + in String packageName, in String databaseName, in String namespace, in List<String> uris, + in Map<String, List<String>> typePropertyPaths, in int userId, in IAppSearchBatchResultCallback callback); /** * Searches a document based on a given specifications. * + * @param packageName The name of the package to query over. * @param databaseName The databaseName this query for. * @param queryExpression String to search for * @param searchSpecBundle SearchSpec bundle @@ -113,6 +128,7 @@ interface IAppSearchManager { * operation. */ void query( + in String packageName, in String databaseName, in String queryExpression, in Bundle searchSpecBundle, @@ -123,6 +139,7 @@ interface IAppSearchManager { * Executes a global query, i.e. over all permitted databases, against the AppSearch index and * returns results. * + * @param packageName The name of the package making the query. * @param queryExpression String to search for * @param searchSpecBundle SearchSpec bundle * @param userId Id of the calling user @@ -130,6 +147,7 @@ interface IAppSearchManager { * operation. */ void globalQuery( + in String packageName, in String queryExpression, in Bundle searchSpecBundle, in int userId, @@ -156,8 +174,39 @@ interface IAppSearchManager { void invalidateNextPageToken(in long nextPageToken, in int userId); /** + * Reports usage of a particular document by URI and namespace. + * + * <p>A usage report represents an event in which a user interacted with or viewed a document. + * + * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency + * metrics for that particular document. These metrics are used for ordering {@link #query} + * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and + * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. + * + * <p>Reporting usage of a document is optional. + * + * @param packageName The name of the package that owns this document. + * @param databaseName The name of the database to report usage against. + * @param namespace Namespace the document being used belongs to. + * @param uri URI of the document being used. + * @param usageTimeMillis The timestamp at which the document was used. + * @param userId Id of the calling user + * @param callback {@link IAppSearchResultCallback#onResult} will be called with an + * {@link AppSearchResult}<{@link Void}>. + */ + void reportUsage( + in String packageName, + in String databaseName, + in String namespace, + in String uri, + in long usageTimeMillis, + in int userId, + in IAppSearchResultCallback callback); + + /** * Removes documents by URI. * + * @param packageName The name of the package the document is in. * @param databaseName The databaseName the document is in. * @param namespace Namespace of the document to remove. * @param uris The URIs of the documents to delete @@ -171,6 +220,7 @@ interface IAppSearchManager { * failure where the {@code throwable} is {@code null}. */ void removeByUri( + in String packageName, in String databaseName, in String namespace, in List<String> uris, @@ -180,6 +230,7 @@ interface IAppSearchManager { /** * Removes documents by given query. * + * @param packageName The name of the package to query over. * @param databaseName The databaseName this query for. * @param queryExpression String to search for * @param searchSpecBundle SearchSpec bundle @@ -188,6 +239,7 @@ interface IAppSearchManager { * {@link AppSearchResult}<{@link Void}>. */ void removeByQuery( + in String packageName, in String databaseName, in String queryExpression, in Bundle searchSpecBundle, diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index 0cb0ea42483e..704509bce2a1 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -47,6 +47,9 @@ public class SearchResults implements Closeable { private final IAppSearchManager mService; + // The package name of the caller. + private final String mPackageName; + // The database name to search over. If null, this will search over all database names. @Nullable private final String mDatabaseName; @@ -68,12 +71,14 @@ public class SearchResults implements Closeable { SearchResults( @NonNull IAppSearchManager service, + @NonNull String packageName, @Nullable String databaseName, @NonNull String queryExpression, @NonNull SearchSpec searchSpec, @UserIdInt int userId, @NonNull @CallbackExecutor Executor executor) { mService = Objects.requireNonNull(service); + mPackageName = packageName; mDatabaseName = databaseName; mQueryExpression = Objects.requireNonNull(queryExpression); mSearchSpec = Objects.requireNonNull(searchSpec); @@ -98,13 +103,12 @@ public class SearchResults implements Closeable { mIsFirstLoad = false; if (mDatabaseName == null) { // Global query, there's no one package-database combination to check. - mService.globalQuery(mQueryExpression, mSearchSpec.getBundle(), mUserId, - wrapCallback(callback)); + mService.globalQuery(mPackageName, mQueryExpression, + mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); } else { // Normal local query, pass in specified database. - mService.query( - mDatabaseName, mQueryExpression, mSearchSpec.getBundle(), mUserId, - wrapCallback(callback)); + mService.query(mPackageName, mDatabaseName, mQueryExpression, + mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); } } else { mService.getNextPage(mNextPageToken, mUserId, wrapCallback(callback)); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java index 9ca363ed9008..d3949047ec5d 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java @@ -19,6 +19,7 @@ package android.app.appsearch; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.AppSearchSchema.PropertyConfig; +import android.app.appsearch.AppSearchSchema.StringPropertyConfig; /** * Encapsulates a {@link GenericDocument} that represent an email. @@ -41,46 +42,40 @@ public class AppSearchEmail extends GenericDocument { public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE) .addProperty( - new PropertyConfig.Builder(KEY_FROM) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_FROM) .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .addProperty( - new PropertyConfig.Builder(KEY_TO) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_TO) .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .addProperty( - new PropertyConfig.Builder(KEY_CC) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_CC) .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .addProperty( - new PropertyConfig.Builder(KEY_BCC) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_BCC) .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .addProperty( - new PropertyConfig.Builder(KEY_SUBJECT) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_SUBJECT) .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .addProperty( - new PropertyConfig.Builder(KEY_BODY) - .setDataType(PropertyConfig.DATA_TYPE_STRING) + new StringPropertyConfig.Builder(KEY_BODY) .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build()) .build(); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java new file mode 100644 index 000000000000..37943fc24ded --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; + +/** + * The helper class for {@link AppSearchSchema} migration. + * + * <p>It will query and migrate {@link GenericDocument} in given type to a new version. + */ +public interface AppSearchMigrationHelper { + + /** + * Queries all documents that need to be migrated to the different version, and transform + * documents to that version by passing them to the provided {@link Transformer}. + * + * @param schemaType The schema that need be updated and migrated {@link GenericDocument} under + * this type. + * @param transformer The {@link Transformer} that will upgrade or downgrade a {@link + * GenericDocument} to new version. + * @see Transformer#transform + */ + // Rethrow the Generic Exception thrown from the Transformer. + @SuppressLint("GenericException") + void queryAndTransform(@NonNull String schemaType, @NonNull Transformer transformer) + throws Exception; + + /** The class to migrate {@link GenericDocument} between different version. */ + interface Transformer { + + /** + * Translates a {@link GenericDocument} from a version to a different version. + * + * <p>If the uri, schema type or namespace is changed via the transform, it will apply to + * the new {@link GenericDocument}. + * + * @param currentVersion The current version of the document's schema. + * @param finalVersion The final version that documents need to be migrated to. + * @param document The {@link GenericDocument} need to be translated to new version. + * @return A {@link GenericDocument} in new version. + */ + @NonNull + // This method will be overridden by users, allow them to throw any customer Exceptions. + @SuppressLint("GenericException") + GenericDocument transform( + int currentVersion, int finalVersion, @NonNull GenericDocument document) + throws Exception; + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java index 62cf38bca44d..2e00ff2320dc 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java @@ -17,6 +17,7 @@ package android.app.appsearch; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -46,6 +47,7 @@ import java.util.Set; */ public final class AppSearchSchema { private static final String SCHEMA_TYPE_FIELD = "schemaType"; + private static final String VERSION_FIELD = "version"; private static final String PROPERTIES_FIELD = "properties"; private final Bundle mBundle; @@ -77,6 +79,11 @@ public final class AppSearchSchema { return mBundle.getString(SCHEMA_TYPE_FIELD, ""); } + /** Returns the version of this {@link AppSearchSchema}. */ + public @IntRange(from = 0) int getVersion() { + return mBundle.getInt(VERSION_FIELD); + } + /** * Returns the list of {@link PropertyConfig}s that are part of this schema. * @@ -91,7 +98,7 @@ public final class AppSearchSchema { } List<PropertyConfig> ret = new ArrayList<>(propertyBundles.size()); for (int i = 0; i < propertyBundles.size(); i++) { - ret.add(new PropertyConfig(propertyBundles.get(i))); + ret.add(PropertyConfig.fromBundle(propertyBundles.get(i))); } return ret; } @@ -108,12 +115,15 @@ public final class AppSearchSchema { if (!getSchemaType().equals(otherSchema.getSchemaType())) { return false; } + if (getVersion() != otherSchema.getVersion()) { + return false; + } return getProperties().equals(otherSchema.getProperties()); } @Override public int hashCode() { - return Objects.hash(getSchemaType(), getProperties()); + return Objects.hash(getSchemaType(), getVersion(), getProperties()); } /** Builder for {@link AppSearchSchema objects}. */ @@ -121,6 +131,7 @@ public final class AppSearchSchema { private final String mSchemaType; private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>(); private final Set<String> mPropertyNames = new ArraySet<>(); + private int mVersion; private boolean mBuilt = false; /** Creates a new {@link AppSearchSchema.Builder}. */ @@ -147,6 +158,42 @@ public final class AppSearchSchema { } /** + * Sets the version number of the {@link AppSearchSchema}. + * + * <p>The {@link AppSearchSession} database can only ever hold documents for one version of + * a {@link AppSearchSchema} type at a time. + * + * <p>Setting a version number that is different from the version number of the schema + * currently stored in AppSearch will result in AppSearch calling the {@link Migrator} + * provided to {@link AppSearchSession#setSchema} to migrate the documents already in + * AppSearch from the previous version to the one set in this request. The version number + * can be updated without any other changes to the schema. + * + * <p>The version number can stay the same, increase, or decrease relative to the current + * version number of the {@link AppSearchSchema} type that is already stored in the {@link + * AppSearchSession} database. + * + * <p>The version number will be updated if the {@link SetSchemaRequest} contains + * backwards-compatible changes or {@link SetSchemaRequest.Builder#setForceOverride} method + * is set to {@code true}. + * + * @param version A non-negative int number represents the version of this {@link + * AppSearchSchema}, default version is 0. + * @throws IllegalStateException if the version is negative or the builder has already been + * used. + * @see AppSearchSession#setSchema + * @see AppSearchSchema.Migrator + * @see SetSchemaRequest.Builder#setMigrator + */ + @NonNull + public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentNonnegative(version); + mVersion = version; + return this; + } + + /** * Constructs a new {@link AppSearchSchema} from the contents of this builder. * * <p>After calling this method, the builder must no longer be used. @@ -156,6 +203,7 @@ public final class AppSearchSchema { Preconditions.checkState(!mBuilt, "Builder has already been used"); Bundle bundle = new Bundle(); bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType); + bundle.putInt(AppSearchSchema.VERSION_FIELD, mVersion); bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles); mBuilt = true; return new AppSearchSchema(bundle); @@ -163,18 +211,15 @@ public final class AppSearchSchema { } /** - * Configuration for a single property (field) of a document type. + * Common configuration for a single property (field) in a Document. * * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a * property. */ - public static final class PropertyConfig { - private static final String NAME_FIELD = "name"; - private static final String DATA_TYPE_FIELD = "dataType"; - private static final String SCHEMA_TYPE_FIELD = "schemaType"; - private static final String CARDINALITY_FIELD = "cardinality"; - private static final String INDEXING_TYPE_FIELD = "indexingType"; - private static final String TOKENIZER_TYPE_FIELD = "tokenizerType"; + public abstract static class PropertyConfig { + static final String NAME_FIELD = "name"; + static final String DATA_TYPE_FIELD = "dataType"; + static final String CARDINALITY_FIELD = "cardinality"; /** * Physical data-types of the contents of the property. @@ -195,18 +240,31 @@ public final class AppSearchSchema { @Retention(RetentionPolicy.SOURCE) public @interface DataType {} + /** @hide */ public static final int DATA_TYPE_STRING = 1; + + /** @hide */ public static final int DATA_TYPE_INT64 = 2; + + /** @hide */ public static final int DATA_TYPE_DOUBLE = 3; + + /** @hide */ public static final int DATA_TYPE_BOOLEAN = 4; - /** Unstructured BLOB. */ + /** + * Unstructured BLOB. + * + * @hide + */ public static final int DATA_TYPE_BYTES = 5; /** * Indicates that the property is itself a {@link GenericDocument}, making it part of a * hierarchical schema. Any property using this DataType MUST have a valid {@link * PropertyConfig#getSchemaType}. + * + * @hide */ public static final int DATA_TYPE_DOCUMENT = 6; @@ -235,6 +293,97 @@ public final class AppSearchSchema { /** Exactly one value [1]. */ public static final int CARDINALITY_REQUIRED = 3; + final Bundle mBundle; + + @Nullable private Integer mHashCode; + + PropertyConfig(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + } + + @Override + public String toString() { + return mBundle.toString(); + } + + /** Returns the name of this property. */ + @NonNull + public String getName() { + return mBundle.getString(NAME_FIELD, ""); + } + + /** Returns the type of data the property contains (e.g. string, int, bytes, etc). */ + public @DataType int getDataType() { + return mBundle.getInt(DATA_TYPE_FIELD, -1); + } + + /** + * Returns the cardinality of the property (whether it is optional, required or repeated). + */ + public @Cardinality int getCardinality() { + return mBundle.getInt(CARDINALITY_FIELD, -1); + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PropertyConfig)) { + return false; + } + PropertyConfig otherProperty = (PropertyConfig) other; + return BundleUtil.deepEquals(this.mBundle, otherProperty.mBundle); + } + + @Override + public int hashCode() { + if (mHashCode == null) { + mHashCode = BundleUtil.deepHashCode(mBundle); + } + return mHashCode; + } + + /** + * Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data + * type. + * + * <p>The bundle is not cloned. + * + * @throws IllegalArgumentException if the bundle does no contain a recognized value in its + * {@code DATA_TYPE_FIELD}. + * @hide + */ + @NonNull + public static PropertyConfig fromBundle(@NonNull Bundle propertyBundle) { + switch (propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD)) { + case PropertyConfig.DATA_TYPE_STRING: + return new StringPropertyConfig(propertyBundle); + case PropertyConfig.DATA_TYPE_INT64: + return new Int64PropertyConfig(propertyBundle); + case PropertyConfig.DATA_TYPE_DOUBLE: + return new DoublePropertyConfig(propertyBundle); + case PropertyConfig.DATA_TYPE_BOOLEAN: + return new BooleanPropertyConfig(propertyBundle); + case PropertyConfig.DATA_TYPE_BYTES: + return new BytesPropertyConfig(propertyBundle); + case PropertyConfig.DATA_TYPE_DOCUMENT: + return new DocumentPropertyConfig(propertyBundle); + default: + throw new IllegalArgumentException( + "Unsupported property bundle of type " + + propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD) + + "; contents: " + + propertyBundle); + } + } + } + + /** Configuration for a property of type String in a Document. */ + public static final class StringPropertyConfig extends PropertyConfig { + private static final String INDEXING_TYPE_FIELD = "indexingType"; + private static final String TOKENIZER_TYPE_FIELD = "tokenizerType"; + /** * Encapsulates the configurations on how AppSearch should query/index these terms. * @@ -249,14 +398,7 @@ public final class AppSearchSchema { @Retention(RetentionPolicy.SOURCE) public @interface IndexingType {} - /** - * Content in this property will not be tokenized or indexed. - * - * <p>Useful if the data type is not made up of terms (e.g. {@link - * PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES} type). None - * of the properties inside the nested property will be indexed regardless of the value of - * {@code indexingType} for the nested properties. - */ + /** Content in this property will not be tokenized or indexed. */ public static final int INDEXING_TYPE_NONE = 0; /** @@ -291,131 +433,375 @@ public final class AppSearchSchema { public @interface TokenizerType {} /** - * It is only valid for tokenizer_type to be 'NONE' if the data type is {@link - * PropertyConfig#DATA_TYPE_DOCUMENT}. + * It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is {@link + * #INDEXING_TYPE_NONE}. */ public static final int TOKENIZER_TYPE_NONE = 0; /** Tokenization for plain text. */ public static final int TOKENIZER_TYPE_PLAIN = 1; - final Bundle mBundle; - - @Nullable private Integer mHashCode; + StringPropertyConfig(@NonNull Bundle bundle) { + super(bundle); + } - PropertyConfig(@NonNull Bundle bundle) { - mBundle = Preconditions.checkNotNull(bundle); + /** Returns how the property is indexed. */ + public @IndexingType int getIndexingType() { + return mBundle.getInt(INDEXING_TYPE_FIELD); } - @Override - public String toString() { - return mBundle.toString(); + /** Returns how this property is tokenized (split into words). */ + public @TokenizerType int getTokenizerType() { + return mBundle.getInt(TOKENIZER_TYPE_FIELD); } - /** Returns the name of this property. */ - @NonNull - public String getName() { - return mBundle.getString(NAME_FIELD, ""); + /** + * Builder for {@link StringPropertyConfig}. + * + * <p>{@link #setCardinality} must be called or {@link #build} will fail. + */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Creates a new {@link StringPropertyConfig.Builder}. */ + public Builder(@NonNull String propertyName) { + mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING); + } + + /** + * The cardinality of the property (whether it is optional, required or repeated). + * + * <p>This property must be set. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass + @NonNull + public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); + mBundle.putInt(CARDINALITY_FIELD, cardinality); + return this; + } + + /** + * Configures how a property should be indexed so that it can be retrieved by queries. + */ + @NonNull + public StringPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType"); + mBundle.putInt(INDEXING_TYPE_FIELD, indexingType); + return this; + } + + /** Configures how this property should be tokenized (split into words). */ + @NonNull + public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType"); + mBundle.putInt(TOKENIZER_TYPE_FIELD, tokenizerType); + return this; + } + + /** + * Constructs a new {@link StringPropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException if the property is not correctly populated + */ + @NonNull + public StringPropertyConfig build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + // TODO(b/147692920): Send the schema to Icing Lib for official validation, instead + // of partially reimplementing some of the validation Icing does here. + if (!mBundle.containsKey(CARDINALITY_FIELD)) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + mBuilt = true; + return new StringPropertyConfig(mBundle); + } } + } - /** Returns the type of data the property contains (e.g. string, int, bytes, etc). */ - public @DataType int getDataType() { - return mBundle.getInt(DATA_TYPE_FIELD, -1); + /** Configuration for a property containing a 64-bit integer. */ + public static final class Int64PropertyConfig extends PropertyConfig { + Int64PropertyConfig(@NonNull Bundle bundle) { + super(bundle); } /** - * Returns the logical schema-type of the contents of this property. + * Builder for {@link Int64PropertyConfig}. * - * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}. Otherwise, - * it is {@code null}. + * <p>{@link #setCardinality} must be called or {@link #build} will fail. */ - @Nullable - public String getSchemaType() { - return mBundle.getString(SCHEMA_TYPE_FIELD); + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Creates a new {@link Int64PropertyConfig.Builder}. */ + public Builder(@NonNull String propertyName) { + mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_INT64); + } + + /** + * The cardinality of the property (whether it is optional, required or repeated). + * + * <p>This property must be set. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass + @NonNull + public Int64PropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); + mBundle.putInt(CARDINALITY_FIELD, cardinality); + return this; + } + + /** + * Constructs a new {@link Int64PropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException if the property is not correctly populated + */ + @NonNull + public Int64PropertyConfig build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + if (!mBundle.containsKey(CARDINALITY_FIELD)) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + mBuilt = true; + return new Int64PropertyConfig(mBundle); + } + } + } + + /** Configuration for a property containing a double-precision decimal number. */ + public static final class DoublePropertyConfig extends PropertyConfig { + DoublePropertyConfig(@NonNull Bundle bundle) { + super(bundle); } /** - * Returns the cardinality of the property (whether it is optional, required or repeated). + * Builder for {@link DoublePropertyConfig}. + * + * <p>{@link #setCardinality} must be called or {@link #build} will fail. */ - public @Cardinality int getCardinality() { - return mBundle.getInt(CARDINALITY_FIELD, -1); - } + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; - /** Returns how the property is indexed. */ - public @IndexingType int getIndexingType() { - return mBundle.getInt(INDEXING_TYPE_FIELD); + /** Creates a new {@link DoublePropertyConfig.Builder}. */ + public Builder(@NonNull String propertyName) { + mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOUBLE); + } + + /** + * The cardinality of the property (whether it is optional, required or repeated). + * + * <p>This property must be set. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass + @NonNull + public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); + mBundle.putInt(CARDINALITY_FIELD, cardinality); + return this; + } + + /** + * Constructs a new {@link DoublePropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException if the property is not correctly populated + */ + @NonNull + public DoublePropertyConfig build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + if (!mBundle.containsKey(CARDINALITY_FIELD)) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + mBuilt = true; + return new DoublePropertyConfig(mBundle); + } } + } - /** Returns how this property is tokenized (split into words). */ - public @TokenizerType int getTokenizerType() { - return mBundle.getInt(TOKENIZER_TYPE_FIELD); + /** Configuration for a property containing a boolean. */ + public static final class BooleanPropertyConfig extends PropertyConfig { + BooleanPropertyConfig(@NonNull Bundle bundle) { + super(bundle); } - @Override - public boolean equals(@Nullable Object other) { - if (this == other) { - return true; + /** + * Builder for {@link BooleanPropertyConfig}. + * + * <p>{@link #setCardinality} must be called or {@link #build} will fail. + */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Creates a new {@link BooleanPropertyConfig.Builder}. */ + public Builder(@NonNull String propertyName) { + mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BOOLEAN); } - if (!(other instanceof PropertyConfig)) { - return false; + + /** + * The cardinality of the property (whether it is optional, required or repeated). + * + * <p>This property must be set. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass + @NonNull + public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkArgumentInRange( + cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); + mBundle.putInt(CARDINALITY_FIELD, cardinality); + return this; } - PropertyConfig otherProperty = (PropertyConfig) other; - return BundleUtil.deepEquals(this.mBundle, otherProperty.mBundle); - } - @Override - public int hashCode() { - if (mHashCode == null) { - mHashCode = BundleUtil.deepHashCode(mBundle); + /** + * Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException if the property is not correctly populated + */ + @NonNull + public BooleanPropertyConfig build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + if (!mBundle.containsKey(CARDINALITY_FIELD)) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + mBuilt = true; + return new BooleanPropertyConfig(mBundle); } - return mHashCode; + } + } + + /** Configuration for a property containing a byte array. */ + public static final class BytesPropertyConfig extends PropertyConfig { + BytesPropertyConfig(@NonNull Bundle bundle) { + super(bundle); } /** - * Builder for {@link PropertyConfig}. - * - * <p>The following properties must be set, or {@link PropertyConfig} construction will - * fail: - * - * <ul> - * <li>dataType - * <li>cardinality - * </ul> + * Builder for {@link BytesPropertyConfig}. * - * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType} - * is also required. + * <p>{@link #setCardinality} must be called or {@link #build} will fail. */ public static final class Builder { private final Bundle mBundle = new Bundle(); private boolean mBuilt = false; - /** Creates a new {@link PropertyConfig.Builder}. */ + /** Creates a new {@link BytesPropertyConfig.Builder}. */ public Builder(@NonNull String propertyName) { mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES); } /** - * Type of data the property contains (e.g. string, int, bytes, etc). + * The cardinality of the property (whether it is optional, required or repeated). * * <p>This property must be set. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass @NonNull - public PropertyConfig.Builder setDataType(@DataType int dataType) { + public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkArgumentInRange( - dataType, DATA_TYPE_STRING, DATA_TYPE_DOCUMENT, "dataType"); - mBundle.putInt(DATA_TYPE_FIELD, dataType); + cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); + mBundle.putInt(CARDINALITY_FIELD, cardinality); return this; } /** + * Constructs a new {@link BytesPropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException if the property is not correctly populated + */ + @NonNull + public BytesPropertyConfig build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + if (!mBundle.containsKey(CARDINALITY_FIELD)) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + mBuilt = true; + return new BytesPropertyConfig(mBundle); + } + } + } + + /** Configuration for a property containing another Document. */ + public static final class DocumentPropertyConfig extends PropertyConfig { + private static final String SCHEMA_TYPE_FIELD = "schemaType"; + private static final String INDEX_NESTED_PROPERTIES_FIELD = "indexNestedProperties"; + + DocumentPropertyConfig(@NonNull Bundle bundle) { + super(bundle); + } + + /** Returns the logical schema-type of the contents of this document property. */ + @NonNull + public String getSchemaType() { + return Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD)); + } + + /** + * Returns whether fields in the nested document should be indexed according to that + * document's schema. + * + * <p>If false, the nested document's properties are not indexed regardless of its own + * schema. + */ + public boolean isIndexNestedProperties() { + return mBundle.getBoolean(INDEX_NESTED_PROPERTIES_FIELD); + } + + /** + * Builder for {@link DocumentPropertyConfig}. + * + * <p>The following properties must be set, or {@link DocumentPropertyConfig} construction + * will fail: + * + * <ul> + * <li>cardinality + * <li>schemaType + * </ul> + */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Creates a new {@link DocumentPropertyConfig.Builder}. */ + public Builder(@NonNull String propertyName) { + mBundle.putString(NAME_FIELD, propertyName); + mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT); + } + + /** * The logical schema-type of the contents of this property. * - * <p>Only required when {@link #setDataType} is set to {@link #DATA_TYPE_DOCUMENT}. - * Otherwise, it is ignored. + * <p>This property must be set. */ @NonNull - public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) { + public DocumentPropertyConfig.Builder setSchemaType(@NonNull String schemaType) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(schemaType); mBundle.putString(SCHEMA_TYPE_FIELD, schemaType); @@ -427,8 +813,9 @@ public final class AppSearchSchema { * * <p>This property must be set. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass @NonNull - public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkArgumentInRange( cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); @@ -437,24 +824,17 @@ public final class AppSearchSchema { } /** - * Configures how a property should be indexed so that it can be retrieved by queries. + * Configures whether fields in the nested document should be indexed according to that + * document's schema. + * + * <p>If false, the nested document's properties are not indexed regardless of its own + * schema. */ @NonNull - public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkArgumentInRange( - indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType"); - mBundle.putInt(INDEXING_TYPE_FIELD, indexingType); - return this; - } - - /** Configures how this property should be tokenized (split into words). */ - @NonNull - public PropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) { + public DocumentPropertyConfig.Builder setIndexNestedProperties( + boolean indexNestedProperties) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkArgumentInRange( - tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType"); - mBundle.putInt(TOKENIZER_TYPE_FIELD, tokenizerType); + mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, indexNestedProperties); return this; } @@ -467,25 +847,56 @@ public final class AppSearchSchema { * missing {@code dataType}). */ @NonNull - public PropertyConfig build() { + public DocumentPropertyConfig build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); - // TODO(b/147692920): Send the schema to Icing Lib for official validation, instead - // of partially reimplementing some of the validation Icing does here. - if (!mBundle.containsKey(DATA_TYPE_FIELD)) { - throw new IllegalSchemaException("Missing field: dataType"); - } - if (mBundle.getString(SCHEMA_TYPE_FIELD, "").isEmpty() - && mBundle.getInt(DATA_TYPE_FIELD) == DATA_TYPE_DOCUMENT) { - throw new IllegalSchemaException( - "Missing field: schemaType (required for configs with " - + "dataType = DOCUMENT)"); + if (mBundle.getString(SCHEMA_TYPE_FIELD, "").isEmpty()) { + throw new IllegalSchemaException("Missing field: schemaType"); } if (!mBundle.containsKey(CARDINALITY_FIELD)) { throw new IllegalSchemaException("Missing field: cardinality"); } mBuilt = true; - return new PropertyConfig(mBundle); + return new DocumentPropertyConfig(mBundle); } } } + + /** + * A migrator class to translate {@link GenericDocument} from different version of {@link + * AppSearchSchema} + */ + public interface Migrator { + + /** + * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}. + * + * <p>This methods will be invoked only if the {@link SetSchemaRequest} is setting a higher + * version number than the current {@link AppSearchSchema} saved in AppSearch. + * + * @param currentVersion The current version of the document's schema. + * @param targetVersion The final version that documents need to be migrated to. + * @param helper The helper class could help to query all documents need to be migrated. + */ + // This method will be overridden by users, allow them to throw any customer Exceptions. + @SuppressLint("GenericException") + default void onUpgrade( + int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper) + throws Exception {} + + /** + * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}. + * + * <p>The methods will be invoked only if the {@link SetSchemaRequest} is setting a higher + * version number than the current {@link AppSearchSchema} saved in AppSearch. + * + * @param currentVersion The current version of the document's schema. + * @param targetVersion The final version that documents need to be migrated to. + * @param helper The helper class could help to query all documents need to be migrated. + */ + // This method will be overridden by users, allow them to throw any customer Exceptions. + @SuppressLint("GenericException") + default void onDowngrade( + int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper) + throws Exception {} + } } 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 11e7fab2b7d9..2f02808bfb62 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java @@ -80,9 +80,9 @@ public class GenericDocument { /** * The maximum number of indexed properties a document can have. * - * <p>Indexed properties are properties where the {@link - * AppSearchSchema.PropertyConfig#getIndexingType()} constant is anything other than {@link - * AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}. + * <p>Indexed properties are properties which are strings where the {@link + * AppSearchSchema.StringPropertyConfig#getIndexingType} value is anything other than {@link + * AppSearchSchema.StringPropertyConfig.IndexingType#INDEXING_TYPE_NONE}. */ public static int getMaxIndexedProperties() { return MAX_INDEXED_PROPERTIES; diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java index 74afdd2c7a80..38e0046f6791 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java @@ -17,13 +17,17 @@ package android.app.appsearch; import android.annotation.NonNull; +import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.util.Preconditions; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -32,12 +36,24 @@ import java.util.Set; * @see AppSearchSession#getByUri */ public final class GetByUriRequest { + /** + * Schema type to be used in {@link android.app.appsearch.GetByUriRequest.Builder#addProjection} + * to apply property paths to all results, excepting any types that have had their own, specific + * property paths set. + */ + public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; + private final String mNamespace; private final Set<String> mUris; + private final Map<String, List<String>> mTypePropertyPathsMap; - GetByUriRequest(@NonNull String namespace, @NonNull Set<String> uris) { - mNamespace = namespace; - mUris = uris; + GetByUriRequest( + @NonNull String namespace, + @NonNull Set<String> uris, + @NonNull Map<String, List<String>> typePropertyPathsMap) { + mNamespace = Preconditions.checkNotNull(namespace); + mUris = Preconditions.checkNotNull(uris); + mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap); } /** Returns the namespace to get documents from. */ @@ -52,10 +68,43 @@ public final class GetByUriRequest { return Collections.unmodifiableSet(mUris); } + /** + * Returns a map from schema type to property paths to be used for projection. + * + * <p>If the map is empty, then all properties will be retrieved for all results. + * + * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this + * function, rather than calling it multiple times. + */ + @NonNull + public Map<String, List<String>> getProjections() { + Map<String, List<String>> copy = new ArrayMap<>(); + for (String key : mTypePropertyPathsMap.keySet()) { + copy.put(key, new ArrayList<>(mTypePropertyPathsMap.get(key))); + } + return copy; + } + + /** + * Returns a map from schema type to property paths to be used for projection. + * + * <p>If the map is empty, then all properties will be retrieved for all results. + * + * <p>A more efficient version of {@link #getProjections}, but it returns a modifiable map. This + * is not meant to be unhidden and should only be used by internal classes. + * + * @hide + */ + @NonNull + public Map<String, List<String>> getProjectionsVisibleToPackagesInternal() { + return mTypePropertyPathsMap; + } + /** Builder for {@link GetByUriRequest} objects. */ public static final class Builder { private String mNamespace = GenericDocument.DEFAULT_NAMESPACE; private final Set<String> mUris = new ArraySet<>(); + private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>(); private boolean mBuilt = false; /** @@ -87,12 +136,63 @@ public final class GetByUriRequest { return this; } + /** + * Adds property paths for the specified type to be used for projection. If property paths + * are added for a type, then only the properties referred to will be retrieved for results + * of that type. If a property path that is specified isn't present in a result, it will be + * ignored for that result. Property paths cannot be null. + * + * <p>If no property paths are added for a particular type, then all properties of results + * of that type will be retrieved. + * + * <p>If property path is added for the {@link + * GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to + * all results, excepting any types that have their own, specific property paths set. + * + * <p>{@see SearchSpec.Builder#addProjection(String, String...)} + */ + @NonNull + public Builder addProjection(@NonNull String schemaType, @NonNull String... propertyPaths) { + Preconditions.checkNotNull(propertyPaths); + return addProjection(schemaType, Arrays.asList(propertyPaths)); + } + + /** + * Adds property paths for the specified type to be used for projection. If property paths + * are added for a type, then only the properties referred to will be retrieved for results + * of that type. If a property path that is specified isn't present in a result, it will be + * ignored for that result. Property paths cannot be null. + * + * <p>If no property paths are added for a particular type, then all properties of results + * of that type will be retrieved. + * + * <p>If property path is added for the {@link + * GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to + * all results, excepting any types that have their own, specific property paths set. + * + * <p>{@see SearchSpec.Builder#addProjection(String, String...)} + */ + @NonNull + public Builder addProjection( + @NonNull String schemaType, @NonNull Collection<String> propertyPaths) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(schemaType); + Preconditions.checkNotNull(propertyPaths); + List<String> propertyPathsList = new ArrayList<>(propertyPaths.size()); + for (String propertyPath : propertyPaths) { + Preconditions.checkNotNull(propertyPath); + propertyPathsList.add(propertyPath); + } + mProjectionTypePropertyPaths.put(schemaType, propertyPathsList); + return this; + } + /** Builds a new {@link GetByUriRequest}. */ @NonNull public GetByUriRequest build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); mBuilt = true; - return new GetByUriRequest(mNamespace, mUris); + return new GetByUriRequest(mNamespace, mUris, mProjectionTypePropertyPaths); } } } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java new file mode 100644 index 000000000000..2bfcf2855430 --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +/** + * A request to report usage of a document. + * + * <p>See {@link AppSearchSession#reportUsage} for a detailed description of usage reporting. + * + * @see AppSearchSession#reportUsage + */ +public final class ReportUsageRequest { + private final String mNamespace; + private final String mUri; + private final long mUsageTimeMillis; + + ReportUsageRequest(@NonNull String namespace, @NonNull String uri, long usageTimeMillis) { + mNamespace = Preconditions.checkNotNull(namespace); + mUri = Preconditions.checkNotNull(uri); + mUsageTimeMillis = usageTimeMillis; + } + + /** Returns the namespace of the document that was used. */ + @NonNull + public String getNamespace() { + return mNamespace; + } + + /** Returns the URI of document that was used. */ + @NonNull + public String getUri() { + return mUri; + } + + /** + * Returns the timestamp in milliseconds of the usage report (the time at which the document was + * used). + * + * <p>The value is in the {@link System#currentTimeMillis} time base. + */ + public long getUsageTimeMillis() { + return mUsageTimeMillis; + } + + /** Builder for {@link ReportUsageRequest} objects. */ + public static final class Builder { + private String mNamespace = GenericDocument.DEFAULT_NAMESPACE; + private String mUri; + private Long mUsageTimeMillis; + private boolean mBuilt = false; + + /** + * Sets which namespace the document being used belongs to. + * + * <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportUsageRequest.Builder setNamespace(@NonNull String namespace) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(namespace); + mNamespace = namespace; + return this; + } + + /** + * Sets the URI of the document being used. + * + * <p>This field is required. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportUsageRequest.Builder setUri(@NonNull String uri) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(uri); + mUri = uri; + return this; + } + + /** + * Sets the timestamp in milliseconds of the usage report (the time at which the document + * was used). + * + * <p>The value is in the {@link System#currentTimeMillis} time base. + * + * <p>If unset, this defaults to the current timestamp at the time that the {@link + * ReportUsageRequest} is constructed. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportUsageRequest.Builder setUsageTimeMillis(long usageTimeMillis) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mUsageTimeMillis = usageTimeMillis; + return this; + } + + /** + * Builds a new {@link ReportUsageRequest}. + * + * @throws NullPointerException if {@link #setUri} has never been called + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportUsageRequest build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI"); + if (mUsageTimeMillis == null) { + mUsageTimeMillis = System.currentTimeMillis(); + } + mBuilt = true; + return new ReportUsageRequest(mNamespace, mUri, mUsageTimeMillis); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java index 62324b2d93f7..d792f450bc05 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java @@ -125,7 +125,6 @@ public final class SearchResult { * Contains the database name that stored the {@link GenericDocument}. * * @return Database name that stored the document - * @hide */ @NonNull public String getDatabaseName() { diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java index b5d5f04dfae7..963062caf500 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java @@ -106,7 +106,9 @@ public final class SearchSpec { RANKING_STRATEGY_NONE, RANKING_STRATEGY_DOCUMENT_SCORE, RANKING_STRATEGY_CREATION_TIMESTAMP, - RANKING_STRATEGY_RELEVANCE_SCORE + RANKING_STRATEGY_RELEVANCE_SCORE, + RANKING_STRATEGY_USAGE_COUNT, + RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP }) @Retention(RetentionPolicy.SOURCE) public @interface RankingStrategy {} @@ -119,6 +121,10 @@ public final class SearchSpec { public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; /** Ranked by document relevance score. */ public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; + /** Ranked by number of usages. */ + public static final int RANKING_STRATEGY_USAGE_COUNT = 4; + /** Ranked by timestamp of last usage. */ + public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; /** * Order for query result. @@ -411,7 +417,7 @@ public final class SearchSpec { Preconditions.checkArgumentInRange( rankingStrategy, RANKING_STRATEGY_NONE, - RANKING_STRATEGY_RELEVANCE_SCORE, + RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP, "Result ranking strategy"); mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy); return this; 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 e9c4cb4e9e34..1486df3a6c29 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java @@ -38,16 +38,19 @@ public final class SetSchemaRequest { private final Set<AppSearchSchema> mSchemas; private final Set<String> mSchemasNotVisibleToSystemUi; private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; + private final Map<String, AppSearchSchema.Migrator> mMigrators; private final boolean mForceOverride; SetSchemaRequest( @NonNull Set<AppSearchSchema> schemas, @NonNull Set<String> schemasNotVisibleToSystemUi, @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, + @NonNull Map<String, AppSearchSchema.Migrator> migrators, boolean forceOverride) { mSchemas = Preconditions.checkNotNull(schemas); mSchemasNotVisibleToSystemUi = Preconditions.checkNotNull(schemasNotVisibleToSystemUi); mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages); + mMigrators = Preconditions.checkNotNull(migrators); mForceOverride = forceOverride; } @@ -81,6 +84,12 @@ public final class SetSchemaRequest { return copy; } + /** Returns the map of {@link android.app.appsearch.AppSearchSchema.Migrator}. */ + @NonNull + public Map<String, AppSearchSchema.Migrator> getMigrators() { + return Collections.unmodifiableMap(mMigrators); + } + /** * 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[] @@ -107,6 +116,7 @@ public final class SetSchemaRequest { private final Set<String> mSchemasNotVisibleToSystemUi = new ArraySet<>(); private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages = new ArrayMap<>(); + private final Map<String, AppSearchSchema.Migrator> mMigrators = new ArrayMap<>(); private boolean mForceOverride = false; private boolean mBuilt = false; @@ -197,6 +207,23 @@ public final class SetSchemaRequest { } /** + * Sets the {@link android.app.appsearch.AppSearchSchema.Migrator}. + * + * @param schemaType The schema type to set migrator on. + * @param migrator The migrator translate a document from it's old version to a new + * incompatible version. + */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects. + public Builder setMigrator( + @NonNull String schemaType, @NonNull AppSearchSchema.Migrator migrator) { + Preconditions.checkNotNull(schemaType); + Preconditions.checkNotNull(migrator); + mMigrators.put(schemaType, migrator); + return this; + } + + /** * Configures the {@link SetSchemaRequest} to delete any existing documents that don't * follow the new schema. * @@ -241,6 +268,7 @@ public final class SetSchemaRequest { mSchemas, mSchemasNotVisibleToSystemUi, mSchemasVisibleToPackages, + mMigrators, mForceOverride); } } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java new file mode 100644 index 000000000000..90a6f60188a6 --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java @@ -0,0 +1,258 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import static android.app.appsearch.AppSearchResult.RESULT_OK; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArraySet; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** The response class of {@link AppSearchSession#setSchema} */ +public class SetSchemaResponse { + private final List<MigrationFailure> mMigrationFailures; + private final Set<String> mDeletedTypes; + private final Set<String> mMigratedTypes; + private final Set<String> mIncompatibleTypes; + private final @AppSearchResult.ResultCode int mResultCode; + + SetSchemaResponse( + @NonNull List<MigrationFailure> migrationFailures, + @NonNull Set<String> deletedTypes, + @NonNull Set<String> migratedTypes, + @NonNull Set<String> incompatibleTypes, + @AppSearchResult.ResultCode int resultCode) { + mMigrationFailures = Preconditions.checkNotNull(migrationFailures); + mDeletedTypes = Preconditions.checkNotNull(deletedTypes); + mMigratedTypes = Preconditions.checkNotNull(migratedTypes); + mIncompatibleTypes = Preconditions.checkNotNull(incompatibleTypes); + mResultCode = resultCode; + } + + /** + * Returns a {@link List} of all failed {@link MigrationFailure}. + * + * <p>A {@link MigrationFailure} will be generated if the system trying to save a post-migrated + * {@link GenericDocument} but fail. + * + * <p>{@link MigrationFailure} contains the uri, namespace and schemaType of the post-migrated + * {@link GenericDocument} and the error reason. Mostly it will be mismatch the schema it + * migrated to. + */ + @NonNull + public List<MigrationFailure> getMigrationFailures() { + return Collections.unmodifiableList(mMigrationFailures); + } + + /** + * Returns a {@link Set} of schema type that were deleted by the {@link + * AppSearchSession#setSchema} call. + */ + @NonNull + public Set<String> getDeletedTypes() { + return Collections.unmodifiableSet(mDeletedTypes); + } + + /** + * Returns a {@link Set} of schema type that were migrated by the {@link + * AppSearchSession#setSchema} call. + */ + @NonNull + public Set<String> getMigratedTypes() { + return Collections.unmodifiableSet(mMigratedTypes); + } + + /** + * Returns a {@link Set} of schema type whose new definitions set in the {@link + * AppSearchSession#setSchema} call were incompatible with the pre-existing schema. + * + * <p>If a {@link android.app.appsearch.AppSearchSchema.Migrator} is provided for this type and + * the migration is success triggered. The type will also appear in {@link #getMigratedTypes()}. + * + * @see AppSearchSession#setSchema + * @see SetSchemaRequest.Builder#setForceOverride + */ + @NonNull + public Set<String> getIncompatibleTypes() { + return Collections.unmodifiableSet(mIncompatibleTypes); + } + + /** Returns {@code true} if all {@link AppSearchSchema}s are successful set to the system. */ + public boolean isSuccess() { + return mResultCode == RESULT_OK; + } + + @Override + @NonNull + public String toString() { + return "{\n Does setSchema success? : " + + isSuccess() + + "\n failures: " + + mMigrationFailures + + "\n}"; + } + + /** + * Builder for {@link SetSchemaResponse} objects. + * + * @hide + */ + public static class Builder { + private final List<MigrationFailure> mMigrationFailures = new ArrayList<>(); + private final Set<String> mDeletedTypes = new ArraySet<>(); + private final Set<String> mMigratedTypes = new ArraySet<>(); + private final Set<String> mIncompatibleTypes = new ArraySet<>(); + private @AppSearchResult.ResultCode int mResultCode = RESULT_OK; + private boolean mBuilt = false; + + /** Adds a {@link MigrationFailure}. */ + @NonNull + public Builder setFailure( + @NonNull String schemaType, + @NonNull String namespace, + @NonNull String uri, + @NonNull AppSearchResult<Void> failureResult) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(schemaType); + Preconditions.checkNotNull(namespace); + Preconditions.checkNotNull(uri); + Preconditions.checkNotNull(failureResult); + Preconditions.checkState(!failureResult.isSuccess()); + mMigrationFailures.add(new MigrationFailure(schemaType, namespace, uri, failureResult)); + return this; + } + + /** Adds a {@link MigrationFailure}. */ + @NonNull + public Builder setFailure( + @NonNull String schemaType, + @NonNull String namespace, + @NonNull String uri, + @AppSearchResult.ResultCode int resultCode, + @Nullable String errorMessage) { + mMigrationFailures.add( + new MigrationFailure( + schemaType, + namespace, + uri, + AppSearchResult.newFailedResult(resultCode, errorMessage))); + return this; + } + + /** Adds deletedTypes to the list of deleted schema types. */ + @NonNull + public Builder addDeletedType(@NonNull Collection<String> deletedTypes) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes)); + return this; + } + + /** Adds incompatibleTypes to the list of incompatible schema types. */ + @NonNull + public Builder addIncompatibleType(@NonNull Collection<String> incompatibleTypes) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes)); + return this; + } + + /** Adds migratedTypes to the list of migrated schema types. */ + @NonNull + public Builder addMigratedType(@NonNull String migratedType) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mMigratedTypes.add(Preconditions.checkNotNull(migratedType)); + return this; + } + + /** Sets the {@link AppSearchResult.ResultCode} of the response. */ + @NonNull + public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mResultCode = resultCode; + return this; + } + + /** Builds a {@link SetSchemaResponse} object. */ + @NonNull + public SetSchemaResponse build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBuilt = true; + return new SetSchemaResponse( + mMigrationFailures, + mDeletedTypes, + mMigratedTypes, + mIncompatibleTypes, + mResultCode); + } + } + + /** + * The class represents a post-migrated {@link GenericDocument} that failed to be saved by + * {@link AppSearchSession#setSchema}. + */ + public static class MigrationFailure { + private final String mSchemaType; + private final String mNamespace; + private final String mUri; + AppSearchResult<Void> mFailureResult; + + MigrationFailure( + @NonNull String schemaType, + @NonNull String namespace, + @NonNull String uri, + @NonNull AppSearchResult<Void> result) { + mSchemaType = schemaType; + mNamespace = namespace; + mUri = uri; + mFailureResult = result; + } + + /** Returns the schema type of the {@link GenericDocument} that fails to be migrated. */ + @NonNull + public String getSchemaType() { + return mSchemaType; + } + + /** Returns the namespace of the {@link GenericDocument} that fails to be migrated. */ + @NonNull + public String getNamespace() { + return mNamespace; + } + + /** Returns the uri of the {@link GenericDocument} that fails to be migrated. */ + @NonNull + public String getUri() { + return mUri; + } + + /** + * Returns the {@link AppSearchResult} that indicates why the post-migrated {@link + * GenericDocument} fails to be saved. + */ + @NonNull + public AppSearchResult<Void> getAppSearchResult() { + return mFailureResult; + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java new file mode 100644 index 000000000000..f04ace684839 --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java @@ -0,0 +1,125 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; +import android.os.Bundle; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class represents the results of setSchema(). + * + * @hide + */ +public class SetSchemaResult { + + public static final String DELETED_SCHEMA_TYPES_FIELD = "deletedSchemaTypes"; + public static final String INCOMPATIBLE_SCHEMA_TYPES_FIELD = "incompatibleSchemaTypes"; + public static final String RESULT_CODE_FIELD = "resultCode"; + private final List<String> mDeletedSchemaTypes; + private final List<String> mIncompatibleSchemaTypes; + private final Bundle mBundle; + + SetSchemaResult(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + mDeletedSchemaTypes = + Preconditions.checkNotNull(mBundle.getStringArrayList(DELETED_SCHEMA_TYPES_FIELD)); + mIncompatibleSchemaTypes = + Preconditions.checkNotNull( + mBundle.getStringArrayList(INCOMPATIBLE_SCHEMA_TYPES_FIELD)); + } + + /** Returns the {@link Bundle} of this class. */ + @NonNull + public Bundle getBundle() { + return mBundle; + } + + /** returns all deleted schema types in this setSchema call. */ + @NonNull + public List<String> getDeletedSchemaTypes() { + return Collections.unmodifiableList(mDeletedSchemaTypes); + } + + /** returns all incompatible schema types in this setSchema call. */ + @NonNull + public List<String> getIncompatibleSchemaTypes() { + return Collections.unmodifiableList(mIncompatibleSchemaTypes); + } + + /** + * returns the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link + * AppSearchSession#setSchema} call. + */ + public int getResultCode() { + return mBundle.getInt(RESULT_CODE_FIELD); + } + + /** Builder for {@link SetSchemaResult} objects. */ + public static final class Builder { + private final ArrayList<String> mDeletedSchemaTypes = new ArrayList<>(); + private final ArrayList<String> mIncompatibleSchemaTypes = new ArrayList<>(); + @AppSearchResult.ResultCode private int mResultCode; + private boolean mBuilt = false; + + /** Adds a deletedSchemaTypes to the {@link SetSchemaResult}. */ + @NonNull + public Builder addDeletedSchemaType(@NonNull String deletedSchemaType) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mDeletedSchemaTypes.add(Preconditions.checkNotNull(deletedSchemaType)); + return this; + } + + /** Adds a incompatible SchemaTypes to the {@link SetSchemaResult}. */ + @NonNull + public Builder addIncompatibleSchemaType(@NonNull String incompatibleSchemaTypes) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mIncompatibleSchemaTypes.add(Preconditions.checkNotNull(incompatibleSchemaTypes)); + return this; + } + + /** + * Sets the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link + * AppSearchSession#setSchema} call to the {@link SetSchemaResult} + */ + @NonNull + public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mResultCode = resultCode; + return this; + } + + /** Builds a {@link SetSchemaResult}. */ + @NonNull + public SetSchemaResult build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Bundle bundle = new Bundle(); + bundle.putStringArrayList( + SetSchemaResult.DELETED_SCHEMA_TYPES_FIELD, mDeletedSchemaTypes); + bundle.putStringArrayList( + SetSchemaResult.INCOMPATIBLE_SCHEMA_TYPES_FIELD, mIncompatibleSchemaTypes); + bundle.putInt(RESULT_CODE_FIELD, mResultCode); + mBuilt = true; + return new SetSchemaResult(bundle); + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index 1f1e9a1a28a8..ed55f0056ea0 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -31,26 +31,30 @@ import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; import android.content.Context; +import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Bundle; import android.os.ParcelableException; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; -/** - * TODO(b/142567528): add comments when implement this class - */ +/** TODO(b/142567528): add comments when implement this class */ public class AppSearchManagerService extends SystemService { private static final String TAG = "AppSearchManagerService"; + private PackageManagerInternal mPackageManagerInternal; + private ImplInstanceManager mImplInstanceManager; public AppSearchManagerService(Context context) { super(context); @@ -59,11 +63,14 @@ public class AppSearchManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.APP_SEARCH_SERVICE, new Stub()); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mImplInstanceManager = new ImplInstanceManager(getContext()); } private class Stub extends IAppSearchManager.Stub { @Override public void setSchema( + @NonNull String packageName, @NonNull String databaseName, @NonNull List<Bundle> schemaBundles, @NonNull List<String> schemasNotPlatformSurfaceable, @@ -71,6 +78,7 @@ public class AppSearchManagerService extends SystemService { boolean forceOverride, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(schemaBundles); Preconditions.checkNotNull(callback); @@ -78,6 +86,7 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { + verifyCallingPackage(callingUid, packageName); List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size()); for (int i = 0; i < schemaBundles.size(); i++) { schemas.add(new AppSearchSchema(schemaBundles.get(i))); @@ -93,8 +102,7 @@ public class AppSearchManagerService extends SystemService { } schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); } - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); impl.setSchema( packageName, databaseName, @@ -102,8 +110,8 @@ public class AppSearchManagerService extends SystemService { schemasNotPlatformSurfaceable, schemasPackageAccessible, forceOverride); - invokeCallbackOnResult(callback, - AppSearchResult.newSuccessfulResult(/*result=*/ null)); + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -113,24 +121,26 @@ public class AppSearchManagerService extends SystemService { @Override public void getSchema( + @NonNull String packageName, @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName); List<Bundle> schemaBundles = new ArrayList<>(schemas.size()); for (int i = 0; i < schemas.size(); i++) { schemaBundles.add(schemas.get(i).getBundle()); } - invokeCallbackOnResult(callback, - AppSearchResult.newSuccessfulResult(schemaBundles)); + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(schemaBundles)); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -140,10 +150,12 @@ public class AppSearchManagerService extends SystemService { @Override public void putDocuments( + @NonNull String packageName, @NonNull String databaseName, @NonNull List<Bundle> documentBundles, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(documentBundles); Preconditions.checkNotNull(callback); @@ -151,10 +163,10 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { + verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { @@ -176,11 +188,14 @@ public class AppSearchManagerService extends SystemService { @Override public void getDocuments( + @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace, @NonNull List<String> uris, + @NonNull Map<String, List<String>> typePropertyPaths, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(uris); @@ -189,15 +204,15 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { + verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Bundle> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { GenericDocument document = impl.getDocument(packageName, databaseName, - namespace, uri); + namespace, uri, typePropertyPaths); resultBuilder.setSuccess(uri, document.getBundle()); } catch (Throwable t) { resultBuilder.setResult(uri, throwableToFailedResult(t)); @@ -214,11 +229,13 @@ public class AppSearchManagerService extends SystemService { // TODO(sidchhabra): Do this in a threadpool. @Override public void query( + @NonNull String packageName, @NonNull String databaseName, @NonNull String queryExpression, @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(queryExpression); Preconditions.checkNotNull(searchSpecBundle); @@ -227,14 +244,16 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); - SearchResultPage searchResultPage = impl.query( - packageName, - databaseName, - queryExpression, - new SearchSpec(searchSpecBundle)); - invokeCallbackOnResult(callback, + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + SearchResultPage searchResultPage = + impl.query( + packageName, + databaseName, + queryExpression, + new SearchSpec(searchSpecBundle)); + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(searchResultPage.getBundle())); } catch (Throwable t) { invokeCallbackOnError(callback, t); @@ -245,10 +264,12 @@ public class AppSearchManagerService extends SystemService { @Override public void globalQuery( + @NonNull String packageName, @NonNull String queryExpression, @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(queryExpression); Preconditions.checkNotNull(searchSpecBundle); Preconditions.checkNotNull(callback); @@ -256,11 +277,15 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); SearchResultPage searchResultPage = impl.globalQuery( queryExpression, - new SearchSpec(searchSpecBundle)); - invokeCallbackOnResult(callback, + new SearchSpec(searchSpecBundle), + packageName, + callingUid); + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(searchResultPage.getBundle())); } catch (Throwable t) { invokeCallbackOnError(callback, t); @@ -270,7 +295,9 @@ public class AppSearchManagerService extends SystemService { } @Override - public void getNextPage(long nextPageToken, @UserIdInt int userId, + public void getNextPage( + long nextPageToken, + @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUid(); @@ -279,9 +306,10 @@ public class AppSearchManagerService extends SystemService { // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally // opened it try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); SearchResultPage searchResultPage = impl.getNextPage(nextPageToken); - invokeCallbackOnResult(callback, + invokeCallbackOnResult( + callback, AppSearchResult.newSuccessfulResult(searchResultPage.getBundle())); } catch (Throwable t) { invokeCallbackOnError(callback, t); @@ -296,7 +324,7 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); impl.invalidateNextPageToken(nextPageToken); } catch (Throwable t) { Log.e(TAG, "Unable to invalidate the query page token", t); @@ -306,12 +334,47 @@ public class AppSearchManagerService extends SystemService { } @Override + public void reportUsage( + @NonNull String packageName, + @NonNull String databaseName, + @NonNull String namespace, + @NonNull String uri, + long usageTimeMillis, + @UserIdInt int userId, + @NonNull IAppSearchResultCallback callback) { + Objects.requireNonNull(databaseName); + Objects.requireNonNull(namespace); + Objects.requireNonNull(uri); + Objects.requireNonNull(callback); + int callingUid = Binder.getCallingUid(); + int callingUserId = handleIncomingUser(userId, callingUid); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + impl.reportUsage( + packageName, + databaseName, + namespace, + uri, + usageTimeMillis); + invokeCallbackOnResult(callback, + AppSearchResult.newSuccessfulResult(/*result=*/ null)); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override public void removeByUri( + @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace, @NonNull List<String> uris, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(uris); Preconditions.checkNotNull(callback); @@ -319,15 +382,15 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { + verifyCallingPackage(callingUid, packageName); AppSearchBatchResult.Builder<String, Void> resultBuilder = new AppSearchBatchResult.Builder<>(); - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); for (int i = 0; i < uris.size(); i++) { String uri = uris.get(i); try { impl.remove(packageName, databaseName, namespace, uri); - resultBuilder.setSuccess(uri, /*result= */null); + resultBuilder.setSuccess(uri, /*result= */ null); } catch (Throwable t) { resultBuilder.setResult(uri, throwableToFailedResult(t)); } @@ -342,11 +405,13 @@ public class AppSearchManagerService extends SystemService { @Override public void removeByQuery( + @NonNull String packageName, @NonNull String databaseName, @NonNull String queryExpression, @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(queryExpression); Preconditions.checkNotNull(searchSpecBundle); @@ -355,9 +420,12 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - String packageName = convertUidToPackageName(callingUid); - impl.removeByQuery(packageName, databaseName, queryExpression, + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); + impl.removeByQuery( + packageName, + databaseName, + queryExpression, new SearchSpec(searchSpecBundle)); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { @@ -373,7 +441,7 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId); impl.persistToDisk(); } catch (Throwable t) { Log.e(TAG, "Unable to persist the data to disk", t); @@ -389,7 +457,7 @@ public class AppSearchManagerService extends SystemService { int callingUserId = handleIncomingUser(userId, callingUid); final long callingIdentity = Binder.clearCallingIdentity(); try { - ImplInstanceManager.getInstance(getContext(), callingUserId); + mImplInstanceManager.getInstance(callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); @@ -398,28 +466,22 @@ public class AppSearchManagerService extends SystemService { } } - /** - * Returns a package name for the given uid. - * - * <p>The current implementation returns the package name of the app with this uid in a - * format like {@code com.example.package} or {@code com.example.sharedname:5678}. - */ - @NonNull - private String convertUidToPackageName(int callingUid) { - // For regular apps, this call will return the package name. If callingUid is an - // android:sharedUserId, this value may be another type of name and have a :uid suffix. - String callingUidName = getContext().getPackageManager().getNameForUid(callingUid); - if (callingUidName == null) { - // Not sure how this is possible --- maybe app was uninstalled? - throw new IllegalStateException( - "Failed to look up package name for uid " + callingUid); + private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) { + Preconditions.checkNotNull(callingPackage); + if (mPackageManagerInternal.getPackageUid( + callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid)) + != callingUid) { + throw new SecurityException( + "Specified calling package [" + + callingPackage + + "] does not match the calling uid " + + callingUid); } - return callingUidName; } /** Invokes the {@link IAppSearchResultCallback} with the result. */ - private void invokeCallbackOnResult(IAppSearchResultCallback callback, - AppSearchResult<?> result) { + private void invokeCallbackOnResult( + IAppSearchResultCallback callback, AppSearchResult<?> result) { try { callback.onResult(result); } catch (RemoteException e) { @@ -428,8 +490,8 @@ public class AppSearchManagerService extends SystemService { } /** Invokes the {@link IAppSearchBatchResultCallback} with the result. */ - private void invokeCallbackOnResult(IAppSearchBatchResultCallback callback, - AppSearchBatchResult<?, ?> result) { + private void invokeCallbackOnResult( + IAppSearchBatchResultCallback callback, AppSearchBatchResult<?, ?> result) { try { callback.onResult(result); } catch (RemoteException e) { @@ -455,8 +517,8 @@ public class AppSearchManagerService extends SystemService { * * <p>The throwable is converted to {@link ParcelableException}. */ - private void invokeCallbackOnError(IAppSearchBatchResultCallback callback, - Throwable throwable) { + private void invokeCallbackOnError( + IAppSearchBatchResultCallback callback, Throwable throwable) { try { callback.onSystemError(new ParcelableException(throwable)); } catch (RemoteException e) { @@ -465,13 +527,18 @@ public class AppSearchManagerService extends SystemService { } } - //TODO(b/173553485) verifying that the caller has permission to access target user's data - //TODO(b/173553485) Handle ACTION_USER_REMOVED broadcast - //TODO(b/173553485) Implement SystemService.onUserStopping() + // TODO(b/173553485) verifying that the caller has permission to access target user's data + // TODO(b/173553485) Handle ACTION_USER_REMOVED broadcast + // TODO(b/173553485) Implement SystemService.onUserStopping() private static int handleIncomingUser(@UserIdInt int userId, int callingUid) { int callingPid = Binder.getCallingPid(); - return ActivityManager.handleIncomingUser(callingPid, callingUid, userId, - /*allowAll=*/ false, /*requireFull=*/ false, - /*name=*/ null, /*callerPackage=*/ null); + return ActivityManager.handleIncomingUser( + callingPid, + callingUid, + userId, + /*allowAll=*/ false, + /*requireFull=*/ false, + /*name=*/ null, + /*callerPackage=*/ null); } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index 2871eb622f11..fe3c2e1d1604 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -16,14 +16,19 @@ package com.android.server.appsearch; +import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Environment; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.SparseArray; +import com.android.internal.R; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import java.io.File; @@ -38,7 +43,13 @@ public final class ImplInstanceManager { private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>(); - private ImplInstanceManager() {} + private final Context mContext; + private final String mGlobalQuerierPackage; + + public ImplInstanceManager(@NonNull Context context) { + mContext = context; + mGlobalQuerierPackage = getGlobalAppSearchDataQuerierPackageName(mContext); + } /** * Gets an instance of AppSearchImpl for the given user. @@ -46,19 +57,18 @@ public final class ImplInstanceManager { * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will * be created. * - * @param context The Android context * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link AppSearchImpl} for this user */ @NonNull - public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) + public AppSearchImpl getInstance(@UserIdInt int userId) throws AppSearchException { AppSearchImpl instance = sInstances.get(userId); if (instance == null) { synchronized (ImplInstanceManager.class) { instance = sInstances.get(userId); if (instance == null) { - instance = createImpl(context, userId); + instance = createImpl(userId); sInstances.put(userId, instance); } } @@ -66,16 +76,41 @@ public final class ImplInstanceManager { return instance; } - private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) + private AppSearchImpl createImpl(@UserIdInt int userId) throws AppSearchException { - File appSearchDir = getAppSearchDir(context, userId); - return AppSearchImpl.create(appSearchDir); + File appSearchDir = getAppSearchDir(mContext, userId); + return AppSearchImpl.create( + appSearchDir, mContext, userId, mGlobalQuerierPackage); } private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs - File userCeDir = Environment.getDataUserCePackageDirectory( - StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName()); + File userCeDir = + Environment.getDataUserCePackageDirectory( + StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName()); return new File(userCeDir, APP_SEARCH_DIR); } + + /** + * Returns the global querier package if it's a system package. Otherwise, empty string. + * + * @param context Context of the system service. + */ + private static String getGlobalAppSearchDataQuerierPackageName(Context context) { + String globalAppSearchDataQuerierPackage = + context.getString(R.string.config_globalAppSearchDataQuerierPackage); + try { + if (context.getPackageManager() + .getPackageInfoAsUser( + globalAppSearchDataQuerierPackage, + MATCH_FACTORY_ONLY, + UserHandle.USER_SYSTEM) + == null) { + return ""; + } + } catch (PackageManager.NameNotFoundException e) { + return ""; + } + return globalAppSearchDataQuerierPackage; + } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index a940ec10b44d..64dc972d301c 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.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,18 +14,26 @@ * limitations under the License. */ +// TODO(b/169883602): This is purposely a different package from the path so that it can access +// AppSearchImpl's methods without having to make them public. This should be moved into a proper +// package once AppSearchImpl-VisibilityStore's dependencies are refactored. package com.android.server.appsearch.external.localstorage; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.exceptions.AppSearchException; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.util.ArrayList; @@ -52,10 +60,18 @@ import java.util.Set; * * <p>NOTE: This class holds an instance of AppSearchImpl and AppSearchImpl holds an instance of * this class. Take care to not cause any circular dependencies. + * + * @hide */ -class VisibilityStore { +public class VisibilityStore { + + private static final String TAG = "AppSearchVisibilityStore"; + + /** No-op user id that won't have any visibility settings. */ + public static final int NO_OP_USER_ID = -1; + /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */ - @VisibleForTesting static final String VISIBILITY_TYPE = "VisibilityType"; + private static final String VISIBILITY_TYPE = "VisibilityType"; /** * Property that holds the list of platform-hidden schemas, as part of the visibility settings. @@ -81,15 +97,14 @@ class VisibilityStore { private static final AppSearchSchema VISIBILITY_SCHEMA = new AppSearchSchema.Builder(VISIBILITY_TYPE) .addProperty( - new AppSearchSchema.PropertyConfig.Builder( + new AppSearchSchema.StringPropertyConfig.Builder( NOT_PLATFORM_SURFACEABLE_PROPERTY) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) .build()) .addProperty( - new AppSearchSchema.PropertyConfig.Builder(PACKAGE_ACCESSIBLE_PROPERTY) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT) + new AppSearchSchema.DocumentPropertyConfig.Builder( + PACKAGE_ACCESSIBLE_PROPERTY) .setSchemaType(PACKAGE_ACCESSIBLE_TYPE) .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) @@ -103,28 +118,26 @@ class VisibilityStore { private static final AppSearchSchema PACKAGE_ACCESSIBLE_SCHEMA = new AppSearchSchema.Builder(PACKAGE_ACCESSIBLE_TYPE) .addProperty( - new AppSearchSchema.PropertyConfig.Builder(PACKAGE_NAME_PROPERTY) + new AppSearchSchema.StringPropertyConfig.Builder(PACKAGE_NAME_PROPERTY) .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) .build()) .addProperty( - new AppSearchSchema.PropertyConfig.Builder(SHA_256_CERT_PROPERTY) + new AppSearchSchema.BytesPropertyConfig.Builder(SHA_256_CERT_PROPERTY) .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES) .build()) .addProperty( - new AppSearchSchema.PropertyConfig.Builder(ACCESSIBLE_SCHEMA_PROPERTY) + new AppSearchSchema.StringPropertyConfig.Builder( + ACCESSIBLE_SCHEMA_PROPERTY) .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) .build()) .build(); /** - * These cannot have any of the special characters used by AppSearchImpl (e.g. {@link - * AppSearchImpl#PACKAGE_DELIMITER} or {@link AppSearchImpl#DATABASE_DELIMITER}. + * These cannot have any of the special characters used by AppSearchImpl (e.g. {@code + * AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}. */ static final String PACKAGE_NAME = "VS#Pkg"; @@ -148,6 +161,16 @@ class VisibilityStore { private final AppSearchImpl mAppSearchImpl; + // Context of the system service. + private final Context mContext; + + // User ID of the caller who we're checking visibility settings for. + private final int mUserId; + + // UID of the package that has platform-query privileges, i.e. can query for all + // platform-surfaceable content. + private int mGlobalQuerierUid; + /** * Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas * in the map are prefixed. @@ -173,8 +196,15 @@ class VisibilityStore { * * @param appSearchImpl AppSearchImpl instance */ - VisibilityStore(@NonNull AppSearchImpl appSearchImpl) { + public VisibilityStore( + @NonNull AppSearchImpl appSearchImpl, + @NonNull Context context, + @UserIdInt int userId, + @NonNull String globalQuerierPackage) { mAppSearchImpl = appSearchImpl; + mContext = context; + mUserId = userId; + mGlobalQuerierUid = getGlobalQuerierUid(globalQuerierPackage); } /** @@ -216,7 +246,8 @@ class VisibilityStore { PACKAGE_NAME, DATABASE_NAME, NAMESPACE, - /*uri=*/ addUriPrefix(prefix)); + /*uri=*/ addUriPrefix(prefix), + /*typePropertyPaths=*/ Collections.emptyMap()); // Update platform visibility settings String[] schemas = @@ -302,8 +333,7 @@ class VisibilityStore { schemasPackageAccessible.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax - // nested - // document uri rules gets synced down. + // nested document uri rules gets synced down. GenericDocument packageAccessibleDocument = new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE) .setNamespace(NAMESPACE) @@ -332,42 +362,93 @@ class VisibilityStore { mPackageAccessibleMap.put(prefix, schemaToPackageIdentifierMap); } - /** Returns if the schema is surfaceable by the platform. */ - // TODO(b/169883602): check permissions against the allowlisted global querier package name. - public boolean isSchemaPlatformSurfaceable( - @NonNull String prefix, @NonNull String prefixedSchema) { + /** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */ + public boolean isSchemaSearchableByCaller( + @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) { Preconditions.checkNotNull(prefix); Preconditions.checkNotNull(prefixedSchema); + + // We compare appIds here rather than direct uids because the package's uid may change based + // on the user that's running. + if (UserHandle.isSameApp(mGlobalQuerierUid, callerUid) + && isSchemaPlatformSurfaceable(prefix, prefixedSchema)) { + return true; + } + + // May not be platform surfaceable, but might still be accessible through 3p access. + return isSchemaPackageAccessible(prefix, prefixedSchema, callerUid); + } + + /** + * Returns whether the caller has platform query privileges, and if so, that the schema is + * surfaceable on the platform. + */ + private boolean isSchemaPlatformSurfaceable( + @NonNull String prefix, @NonNull String prefixedSchema) { + if (prefix.equals(VISIBILITY_STORE_PREFIX)) { + // VisibilityStore schemas are for internal bookkeeping. + return false; + } + Set<String> notPlatformSurfaceableSchemas = mNotPlatformSurfaceableMap.get(prefix); if (notPlatformSurfaceableSchemas == null) { + // No schemas were opted out of being platform-surfaced. So by default, it can be + // surfaced. return true; } + + // Some schemas were opted out of being platform-surfaced. As long as this schema + // isn't one of those opt-outs, it's surfaceable. return !notPlatformSurfaceableSchemas.contains(prefixedSchema); } - /** Returns whether the schema is accessible by {@code accessingPackage}. */ - // TODO(b/169883602): check certificate and package against the incoming querier's uid/package. - public boolean isSchemaPackageAccessible( - @NonNull String prefix, - @NonNull String prefixedSchema, - @NonNull PackageIdentifier accessingPackage) { - Preconditions.checkNotNull(prefix); - Preconditions.checkNotNull(prefixedSchema); - Preconditions.checkNotNull(accessingPackage); - + /** + * Returns whether the schema is accessible by the {@code callerUid}. Checks that the callerUid + * has one of the allowed PackageIdentifier's package. And if so, that the package also has the + * matching certificate. + * + * <p>This supports packages that have certificate rotation. As long as the specified + * certificate was once used to sign the package, the package will still be granted access. This + * does not handle packages that have been signed by multiple certificates. + */ + private boolean isSchemaPackageAccessible( + @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) { Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = mPackageAccessibleMap.get(prefix); if (schemaToPackageIdentifierMap == null) { + // No schemas under this prefix have granted package access, return early. return false; } Set<PackageIdentifier> packageIdentifiers = schemaToPackageIdentifierMap.get(prefixedSchema); if (packageIdentifiers == null) { + // No package identifiers were granted access for this schema, return early. return false; } - return packageIdentifiers.contains(accessingPackage); + for (PackageIdentifier packageIdentifier : packageIdentifiers) { + // Check that the caller uid matches this allowlisted PackageIdentifier. + // TODO(b/169883602): Consider caching the UIDs of packages. Looking this up in the + // package manager could be costly. We would also need to update the cache on + // package-removals. + if (getPackageUidAsUser(packageIdentifier.getPackageName()) != callerUid) { + continue; + } + + // Check that the package also has the matching certificate + if (mContext.getPackageManager() + .hasSigningCertificate( + packageIdentifier.getPackageName(), + packageIdentifier.getSha256Certificate(), + PackageManager.CERT_INPUT_SHA256)) { + // The caller has the right package name and right certificate! + return true; + } + } + + // If we can't verify the schema is package accessible, default to no access. + return false; } /** @@ -375,7 +456,7 @@ class VisibilityStore { * * <p>{@link #initialize()} must be called after this. */ - void handleReset() { + public void handleReset() { mNotPlatformSurfaceableMap.clear(); mPackageAccessibleMap.clear(); } @@ -389,4 +470,42 @@ class VisibilityStore { private static String addUriPrefix(String uri) { return URI_PREFIX + uri; } + + /** + * Finds the uid of the {@code globalQuerierPackage}. {@code globalQuerierPackage} must be a + * pre-installed, system app. Returns {@link Process#INVALID_UID} if unable to find the UID. + */ + private int getGlobalQuerierUid(@NonNull String globalQuerierPackage) { + try { + int flags = + PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY; + // It doesn't matter that we're using the caller's userId here. We'll eventually check + // that the two uids in question belong to the same appId. + return mContext.getPackageManager() + .getPackageUidAsUser(globalQuerierPackage, flags, mUserId); + } catch (PackageManager.NameNotFoundException e) { + // Global querier doesn't exist. + Log.i( + TAG, + "AppSearch global querier package not found on device: '" + + globalQuerierPackage + + "'"); + } + return Process.INVALID_UID; + } + + /** + * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to + * find the UID. + */ + private int getPackageUidAsUser(@NonNull String packageName) { + try { + return mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId); + } catch (PackageManager.NameNotFoundException e) { + // Package doesn't exist, continue + } + return Process.INVALID_UID; + } } 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 674f199f0022..592b8b97db60 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 @@ -21,10 +21,13 @@ import android.annotation.WorkerThread; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; +import android.app.appsearch.GetByUriRequest; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; +import android.app.appsearch.SetSchemaResult; import android.app.appsearch.exceptions.AppSearchException; +import android.content.Context; import android.os.Bundle; import android.util.ArrayMap; import android.util.ArraySet; @@ -34,9 +37,12 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter; +import com.android.server.appsearch.external.localstorage.converter.ResultCodeToProtoConverter; import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter; import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter; import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter; +import com.android.server.appsearch.external.localstorage.converter.SetSchemaResultToProtoConverter; +import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter; import com.google.android.icing.IcingSearchEngine; import com.google.android.icing.proto.DeleteByQueryResultProto; @@ -54,6 +60,7 @@ import com.google.android.icing.proto.PersistToDiskResultProto; import com.google.android.icing.proto.PropertyConfigProto; import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.PutResultProto; +import com.google.android.icing.proto.ReportUsageResultProto; import com.google.android.icing.proto.ResetResultProto; import com.google.android.icing.proto.ResultSpecProto; import com.google.android.icing.proto.SchemaProto; @@ -64,6 +71,7 @@ import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.SetSchemaResultProto; import com.google.android.icing.proto.StatusProto; import com.google.android.icing.proto.TypePropertyMask; +import com.google.android.icing.proto.UsageReport; import java.io.File; import java.util.ArrayList; @@ -154,14 +162,27 @@ public final class AppSearchImpl { * folder. */ @NonNull - public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException { + public static AppSearchImpl create( + @NonNull File icingDir, + @NonNull Context context, + int userId, + @NonNull String globalQuerierPackage) + throws AppSearchException { Preconditions.checkNotNull(icingDir); - AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir); + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(globalQuerierPackage); + AppSearchImpl appSearchImpl = + new AppSearchImpl(icingDir, context, userId, globalQuerierPackage); appSearchImpl.initializeVisibilityStore(); return appSearchImpl; } - private AppSearchImpl(@NonNull File icingDir) throws AppSearchException { + private AppSearchImpl( + @NonNull File icingDir, + @NonNull Context context, + int userId, + @NonNull String globalQuerierPackage) + throws AppSearchException { mReadWriteLock.writeLock().lock(); try { @@ -173,7 +194,9 @@ public final class AppSearchImpl { .build(); mIcingSearchEngineLocked = new IcingSearchEngine(options); - mVisibilityStoreLocked = new VisibilityStore(this); + mVisibilityStoreLocked = + new VisibilityStore( + this, context, userId, globalQuerierPackage); InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize(); SchemaProto schemaProto; @@ -239,7 +262,8 @@ public final class AppSearchImpl { * which do not comply with the new schema will be deleted. * @throws AppSearchException on IcingSearchEngine error. */ - public void setSchema( + @NonNull + public SetSchemaResult setSchema( @NonNull String packageName, @NonNull String databaseName, @NonNull List<AppSearchSchema> schemas, @@ -253,8 +277,9 @@ public final class AppSearchImpl { SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder(); for (int i = 0; i < schemas.size(); i++) { + AppSearchSchema schema = schemas.get(i); SchemaTypeConfigProto schemaTypeProto = - SchemaToProtoConverter.toSchemaTypeConfigProto(schemas.get(i)); + SchemaToProtoConverter.toSchemaTypeConfigProto(schema); newSchemaBuilder.addTypes(schemaTypeProto); } @@ -273,16 +298,10 @@ public final class AppSearchImpl { try { checkSuccess(setSchemaResultProto.getStatus()); } catch (AppSearchException e) { - // Improve the error message by merging in information about incompatible types. if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) { - String newMessage = - e.getMessage() - + "\n Deleted types: " - + setSchemaResultProto.getDeletedSchemaTypesList() - + "\n Incompatible types: " - + setSchemaResultProto.getIncompatibleSchemaTypesList(); - throw new AppSearchException(e.getResultCode(), newMessage, e.getCause()); + return SetSchemaResultToProtoConverter.toSetSchemaResult( + setSchemaResultProto, prefix); } else { throw e; } @@ -319,6 +338,7 @@ public final class AppSearchImpl { // incompatible schemas. checkForOptimizeLocked(/* force= */ true); } + return SetSchemaResultToProtoConverter.toSetSchemaResult(setSchemaResultProto, prefix); } finally { mReadWriteLock.writeLock().unlock(); } @@ -419,6 +439,8 @@ public final class AppSearchImpl { * @param databaseName The databaseName this document resides in. * @param namespace The namespace this document resides in. * @param uri The URI of the document to get. + * @param typePropertyPaths A map of schema type to a list of property paths to return in the + * result. * @return The Document contents * @throws AppSearchException on IcingSearchEngine error. */ @@ -427,16 +449,35 @@ public final class AppSearchImpl { @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace, - @NonNull String uri) + @NonNull String uri, + @NonNull Map<String, List<String>> typePropertyPaths) throws AppSearchException { GetResultProto getResultProto; + List<TypePropertyMask> nonPrefixedPropertyMasks = + TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths); + List<TypePropertyMask> prefixedPropertyMasks = + new ArrayList<>(nonPrefixedPropertyMasks.size()); + for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) { + TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i); + String nonPrefixedType = typePropertyMask.getSchemaType(); + String prefixedType = + nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD) + ? nonPrefixedType + : createPrefix(packageName, databaseName) + nonPrefixedType; + prefixedPropertyMasks.add( + typePropertyMask.toBuilder().setSchemaType(prefixedType).build()); + } + GetResultSpecProto getResultSpec = + GetResultSpecProto.newBuilder() + .addAllTypePropertyMasks(prefixedPropertyMasks) + .build(); mReadWriteLock.readLock().lock(); try { getResultProto = mIcingSearchEngineLocked.get( createPrefix(packageName, databaseName) + namespace, uri, - GetResultSpecProto.getDefaultInstance()); + getResultSpec); } finally { mReadWriteLock.readLock().unlock(); } @@ -476,8 +517,12 @@ public final class AppSearchImpl { mReadWriteLock.readLock().lock(); try { + String prefix = createPrefix(packageName, databaseName); + Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec); + return doQueryLocked( Collections.singleton(createPrefix(packageName, databaseName)), + allowedPrefixedSchemas, queryExpression, searchSpec); } finally { @@ -493,40 +538,72 @@ public final class AppSearchImpl { * * @param queryExpression Query String to search. * @param searchSpec Spec for setting filters, raw query etc. + * @param callerPackageName Package name of the caller, should belong to the {@code callerUid}. + * @param callerUid UID of the client making the globalQuery call. * @return The results of performing this search. It may contain an empty list of results if no * documents matched the query. * @throws AppSearchException on IcingSearchEngine error. */ @NonNull public SearchResultPage globalQuery( - @NonNull String queryExpression, @NonNull SearchSpec searchSpec) + @NonNull String queryExpression, + @NonNull SearchSpec searchSpec, + @NonNull String callerPackageName, + int callerUid) throws AppSearchException { - // TODO(b/169883602): Check if the platform is querying us at a higher level. At this - // point, we should add all platform-surfaceable schemas assuming the querier has been - // verified. mReadWriteLock.readLock().lock(); try { - Set<String> prefixes = new ArraySet<>(); Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames()); - - for (String prefix : mNamespaceMapLocked.keySet()) { - if (prefix.equals(VisibilityStore.VISIBILITY_STORE_PREFIX)) { - // Filter out any VisibilityStore documents which are AppSearch-internal only. - continue; + Set<String> prefixFilters = new ArraySet<>(); + Set<String> allPrefixes = mNamespaceMapLocked.keySet(); + if (packageFilters.isEmpty()) { + // Client didn't restrict their search over packages. Try to query over all + // packages/prefixes + prefixFilters = allPrefixes; + } else { + // Client did restrict their search over packages. Only include the prefixes that + // belong to the specified packages. + for (String prefix : allPrefixes) { + String packageName = getPackageName(prefix); + if (packageFilters.contains(packageName)) { + prefixFilters.add(prefix); + } } + } - if (!packageFilters.isEmpty() && !packageFilters.contains(getPackageName(prefix))) { - // Client wanted to restrict search over specified packages. Since the - // specified packages don't include this prefix, don't add it to our search - // filters. - continue; + // Find which schemas the client is allowed to query over. + Set<String> allowedPrefixedSchemas = new ArraySet<>(); + List<String> schemaFilters = searchSpec.getSchemaTypes(); + for (String prefix : prefixFilters) { + String packageName = getPackageName(prefix); + + if (!schemaFilters.isEmpty()) { + for (String schema : schemaFilters) { + // Client specified some schemas to search over, check each one + String prefixedSchema = prefix + schema; + if (packageName.equals(callerPackageName) + || mVisibilityStoreLocked.isSchemaSearchableByCaller( + prefix, prefixedSchema, callerUid)) { + allowedPrefixedSchemas.add(prefixedSchema); + } + } + } else { + // Client didn't specify certain schemas to search over, check all schemas + Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix); + if (prefixedSchemas != null) { + for (String prefixedSchema : prefixedSchemas) { + if (packageName.equals(callerPackageName) + || mVisibilityStoreLocked.isSchemaSearchableByCaller( + prefix, prefixedSchema, callerUid)) { + allowedPrefixedSchemas.add(prefixedSchema); + } + } + } } - - // Otherwise, include this prefix in our global search. - prefixes.add(prefix); } - return doQueryLocked(prefixes, queryExpression, searchSpec); + return doQueryLocked( + prefixFilters, allowedPrefixedSchemas, queryExpression, searchSpec); } finally { mReadWriteLock.readLock().unlock(); } @@ -535,28 +612,25 @@ public final class AppSearchImpl { @GuardedBy("mReadWriteLock") private SearchResultPage doQueryLocked( @NonNull Set<String> prefixes, + @NonNull Set<String> allowedPrefixedSchemas, @NonNull String queryExpression, @NonNull SearchSpec searchSpec) throws AppSearchException { SearchSpecProto.Builder searchSpecBuilder = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec).toBuilder() .setQuery(queryExpression); - // rewriteSearchSpecForPrefixesLocked will return false if none of the prefixes that the - // client is trying to search on exist, so we can return an empty SearchResult and skip + // rewriteSearchSpecForPrefixesLocked will return false if there is nothing to search + // over given their search filters, so we can return an empty SearchResult and skip // sending request to Icing. - if (!rewriteSearchSpecForPrefixesLocked(searchSpecBuilder, prefixes)) { + if (!rewriteSearchSpecForPrefixesLocked( + searchSpecBuilder, prefixes, allowedPrefixedSchemas)) { return new SearchResultPage(Bundle.EMPTY); } ResultSpecProto.Builder resultSpecBuilder = SearchSpecToProtoConverter.toResultSpecProto(searchSpec).toBuilder(); - // rewriteResultSpecForPrefixesLocked will return false if none of the prefixes that the - // client is trying to search on exist, so we can return an empty SearchResult and skip - // sending request to Icing. - if (!rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes)) { - return new SearchResultPage(Bundle.EMPTY); - } + rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes, allowedPrefixedSchemas); ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec); SearchResultProto searchResultProto = @@ -607,6 +681,31 @@ public final class AppSearchImpl { } } + /** Reports a usage of the given document at the given timestamp. */ + public void reportUsage( + @NonNull String packageName, + @NonNull String databaseName, + @NonNull String namespace, + @NonNull String uri, + long usageTimestampMillis) + throws AppSearchException { + String prefixedNamespace = createPrefix(packageName, databaseName) + namespace; + UsageReport report = + UsageReport.newBuilder() + .setDocumentNamespace(prefixedNamespace) + .setDocumentUri(uri) + .setUsageTimestampMs(usageTimestampMillis) + .setUsageType(UsageReport.UsageType.USAGE_TYPE1) + .build(); + mReadWriteLock.writeLock().lock(); + try { + ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report); + checkSuccess(result.getStatus()); + } finally { + mReadWriteLock.writeLock().unlock(); + } + } + /** * Removes the given document by URI. * @@ -667,12 +766,14 @@ public final class AppSearchImpl { DeleteByQueryResultProto deleteResultProto; mReadWriteLock.writeLock().lock(); try { - // Only rewrite SearchSpec for non empty prefixes. - // rewriteSearchSpecForPrefixesLocked will return false for empty prefixes, we - // should skip sending request to Icing and return in here. + String prefix = createPrefix(packageName, databaseName); + Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec); + + // rewriteSearchSpecForPrefixesLocked will return false if there is nothing to search + // over given their search filters, so we can return early and skip sending request + // to Icing. if (!rewriteSearchSpecForPrefixesLocked( - searchSpecBuilder, - Collections.singleton(createPrefix(packageName, databaseName)))) { + searchSpecBuilder, Collections.singleton(prefix), allowedPrefixedSchemas)) { return; } deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build()); @@ -695,6 +796,8 @@ public final class AppSearchImpl { * <p>If the app crashes before a call to PersistToDisk(), Icing would trigger a costly recovery * process in next initialization. After that, Icing would still be able to recover all written * data. + * + * @throws AppSearchException on any error that AppSearch persist data to disk. */ public void persistToDisk() throws AppSearchException { PersistToDiskResultProto persistToDiskResultProto = @@ -904,19 +1007,23 @@ public final class AppSearchImpl { } /** - * Rewrites the schemaTypeFilters and namespacesFilters that exist with {@code prefixes}. - * - * <p>If the searchSpec has empty filter lists, all prefixes filters will be added. + * Rewrites the search spec filters with {@code prefixes}. * * <p>This method should be only called in query methods and get the READ lock to keep thread * safety. * - * @return false if none of the requested prefixes exist. + * @param searchSpecBuilder Client-provided SearchSpec + * @param prefixes Prefixes that we should prepend to all our filters + * @param allowedPrefixedSchemas Prefixed schemas that the client is allowed to query over. This + * supersedes the schema filters that may exist on the {@code searchSpecBuilder}. + * @return false if none there would be nothing to search over. */ @VisibleForTesting @GuardedBy("mReadWriteLock") boolean rewriteSearchSpecForPrefixesLocked( - @NonNull SearchSpecProto.Builder searchSpecBuilder, @NonNull Set<String> prefixes) { + @NonNull SearchSpecProto.Builder searchSpecBuilder, + @NonNull Set<String> prefixes, + @NonNull Set<String> allowedPrefixedSchemas) { // Create a copy since retainAll() modifies the original set. Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); existingPrefixes.retainAll(prefixes); @@ -926,29 +1033,28 @@ public final class AppSearchImpl { return false; } - // Cache the schema type filters and namespaces before clearing everything. - List<String> schemaTypeFilters = searchSpecBuilder.getSchemaTypeFiltersList(); + if (allowedPrefixedSchemas.isEmpty()) { + // Not allowed to search over any schemas, empty query. + return false; + } + + // Clear the schema type filters since we'll be rewriting them with the + // allowedPrefixedSchemas. searchSpecBuilder.clearSchemaTypeFilters(); + searchSpecBuilder.addAllSchemaTypeFilters(allowedPrefixedSchemas); + // Cache the namespaces before clearing everything. List<String> namespaceFilters = searchSpecBuilder.getNamespaceFiltersList(); searchSpecBuilder.clearNamespaceFilters(); - // Rewrite filters to include a prefix. + // Rewrite non-schema filters to include a prefix. for (String prefix : existingPrefixes) { - Set<String> existingSchemaTypes = mSchemaMapLocked.get(prefix); - if (schemaTypeFilters.isEmpty()) { - // Include all schema types - searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes); - } else { - // Add the prefix to the given schema types - for (int i = 0; i < schemaTypeFilters.size(); i++) { - String prefixedType = prefix + schemaTypeFilters.get(i); - if (existingSchemaTypes.contains(prefixedType)) { - searchSpecBuilder.addSchemaTypeFilters(prefixedType); - } - } - } + // TODO(b/169883602): We currently grab every namespace for every prefix. We can + // optimize this by checking if a prefix has any allowedSchemaTypes. If not, that + // means we don't want to query over anything in that prefix anyways, so we don't + // need to grab its namespaces either. + // Empty namespaces on the search spec means to query over all namespaces. Set<String> existingNamespaces = mNamespaceMapLocked.get(prefix); if (namespaceFilters.isEmpty()) { // Include all namespaces @@ -968,43 +1074,70 @@ public final class AppSearchImpl { } /** + * Returns the set of allowed prefixed schemas that the {@code prefix} can query while taking + * into account the {@code searchSpec} schema filters. + * + * <p>This only checks intersection of schema filters on the search spec with those that the + * prefix owns itself. This does not check global query permissions. + */ + private Set<String> getAllowedPrefixSchemas( + @NonNull String prefix, @NonNull SearchSpec searchSpec) { + Set<String> allowedPrefixedSchemas = new ArraySet<>(); + + // Add all the schema filters the client specified. + List<String> schemaFilters = searchSpec.getSchemaTypes(); + for (int i = 0; i < schemaFilters.size(); i++) { + allowedPrefixedSchemas.add(prefix + schemaFilters.get(i)); + } + + if (allowedPrefixedSchemas.isEmpty()) { + // If the client didn't specify any schema filters, search over all of their schemas + Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix); + if (prefixedSchemas != null) { + allowedPrefixedSchemas.addAll(prefixedSchemas); + } + } + return allowedPrefixedSchemas; + } + + /** * Rewrites the typePropertyMasks that exist in {@code prefixes}. * * <p>This method should be only called in query methods and get the READ lock to keep thread * safety. * - * @return false if none of the requested prefixes exist. + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param allowedPrefixedSchemas Prefixed schemas that the client is allowed to query over. */ @VisibleForTesting @GuardedBy("mReadWriteLock") - boolean rewriteResultSpecForPrefixesLocked( - @NonNull ResultSpecProto.Builder resultSpecBuilder, @NonNull Set<String> prefixes) { + void rewriteResultSpecForPrefixesLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + @NonNull Set<String> allowedPrefixedSchemas) { // Create a copy since retainAll() modifies the original set. Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); existingPrefixes.retainAll(prefixes); - if (existingPrefixes.isEmpty()) { - // None of the prefixes exist, empty query. - return false; - } - List<TypePropertyMask> prefixedTypePropertyMasks = new ArrayList<>(); // Rewrite filters to include a database prefix. for (String prefix : existingPrefixes) { - Set<String> existingSchemaTypes = mSchemaMapLocked.get(prefix); // Qualify the given schema types for (TypePropertyMask typePropertyMask : resultSpecBuilder.getTypePropertyMasksList()) { - String qualifiedType = prefix + typePropertyMask.getSchemaType(); - if (existingSchemaTypes.contains(qualifiedType)) { + String unprefixedType = typePropertyMask.getSchemaType(); + boolean isWildcard = + unprefixedType.equals(SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD); + String prefixedType = isWildcard ? unprefixedType : prefix + unprefixedType; + if (isWildcard || allowedPrefixedSchemas.contains(prefixedType)) { prefixedTypePropertyMasks.add( - typePropertyMask.toBuilder().setSchemaType(qualifiedType).build()); + typePropertyMask.toBuilder().setSchemaType(prefixedType).build()); } } } resultSpecBuilder .clearTypePropertyMasks() .addAllTypePropertyMasks(prefixedTypePropertyMasks); - return true; } @VisibleForTesting @@ -1228,6 +1361,7 @@ public final class AppSearchImpl { } @GuardedBy("mReadWriteLock") + @NonNull @VisibleForTesting VisibilityStore getVisibilityStoreLocked() { return mVisibilityStoreLocked; @@ -1242,28 +1376,8 @@ public final class AppSearchImpl { * @return AppSearchException with the parallel error code. */ private static AppSearchException statusProtoToAppSearchException(StatusProto statusProto) { - switch (statusProto.getCode()) { - case INVALID_ARGUMENT: - return new AppSearchException( - AppSearchResult.RESULT_INVALID_ARGUMENT, statusProto.getMessage()); - case NOT_FOUND: - return new AppSearchException( - AppSearchResult.RESULT_NOT_FOUND, statusProto.getMessage()); - case FAILED_PRECONDITION: - // Fallthrough - case ABORTED: - // Fallthrough - case INTERNAL: - return new AppSearchException( - AppSearchResult.RESULT_INTERNAL_ERROR, statusProto.getMessage()); - case OUT_OF_SPACE: - return new AppSearchException( - AppSearchResult.RESULT_OUT_OF_SPACE, statusProto.getMessage()); - default: - // Some unknown/unsupported error - return new AppSearchException( - AppSearchResult.RESULT_UNKNOWN_ERROR, - "Unknown IcingSearchEngine status code: " + statusProto.getCode()); - } + return new AppSearchException( + ResultCodeToProtoConverter.toResultCode(statusProto.getCode()), + statusProto.getMessage()); } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java new file mode 100644 index 000000000000..e340de0a5802 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java @@ -0,0 +1,62 @@ +/* + * 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.converter; + +import android.annotation.NonNull; +import android.app.appsearch.AppSearchResult; +import android.util.Log; + +import com.google.android.icing.proto.StatusProto; + +/** + * Translates an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode} + * + * @hide + */ +public final class ResultCodeToProtoConverter { + + private static final String TAG = "AppSearchResultCodeToPr"; + + private ResultCodeToProtoConverter() {} + + /** Converts an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode}. */ + public static @AppSearchResult.ResultCode int toResultCode( + @NonNull StatusProto.Code statusCode) { + switch (statusCode) { + case OK: + return AppSearchResult.RESULT_OK; + case OUT_OF_SPACE: + return AppSearchResult.RESULT_OUT_OF_SPACE; + case INTERNAL: + return AppSearchResult.RESULT_INTERNAL_ERROR; + case UNKNOWN: + return AppSearchResult.RESULT_UNKNOWN_ERROR; + case NOT_FOUND: + return AppSearchResult.RESULT_NOT_FOUND; + case INVALID_ARGUMENT: + return AppSearchResult.RESULT_INVALID_ARGUMENT; + default: + // Some unknown/unsupported error + Log.e( + TAG, + "Cannot convert IcingSearchEngine status code: " + + statusCode + + " to AppSearchResultCode."); + return AppSearchResult.RESULT_INTERNAL_ERROR; + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java index 4165af31c00b..ce1c9f4d4744 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java @@ -22,6 +22,7 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import com.google.android.icing.proto.DocumentIndexingConfig; import com.google.android.icing.proto.PropertyConfigProto; import com.google.android.icing.proto.SchemaTypeConfigProto; import com.google.android.icing.proto.SchemaTypeConfigProtoOrBuilder; @@ -48,7 +49,9 @@ public final class SchemaToProtoConverter { public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema) { Preconditions.checkNotNull(schema); SchemaTypeConfigProto.Builder protoBuilder = - SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaType()); + SchemaTypeConfigProto.newBuilder() + .setSchemaType(schema.getSchemaType()) + .setVersion(schema.getVersion()); List<AppSearchSchema.PropertyConfig> properties = schema.getProperties(); for (int i = 0; i < properties.size(); i++) { PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i)); @@ -63,7 +66,6 @@ public final class SchemaToProtoConverter { Preconditions.checkNotNull(property); PropertyConfigProto.Builder builder = PropertyConfigProto.newBuilder().setPropertyName(property.getName()); - StringIndexingConfig.Builder indexingConfig = StringIndexingConfig.newBuilder(); // Set dataType @AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType(); @@ -74,12 +76,6 @@ public final class SchemaToProtoConverter { } builder.setDataType(dataTypeProto); - // Set schemaType - String schemaType = property.getSchemaType(); - if (schemaType != null) { - builder.setSchemaType(schemaType); - } - // Set cardinality @AppSearchSchema.PropertyConfig.Cardinality int cardinality = property.getCardinality(); PropertyConfigProto.Cardinality.Code cardinalityProto = @@ -89,36 +85,27 @@ public final class SchemaToProtoConverter { } builder.setCardinality(cardinalityProto); - // Set indexingType - @AppSearchSchema.PropertyConfig.IndexingType int indexingType = property.getIndexingType(); - TermMatchType.Code termMatchTypeProto; - switch (indexingType) { - case AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE: - termMatchTypeProto = TermMatchType.Code.UNKNOWN; - break; - case AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS: - termMatchTypeProto = TermMatchType.Code.EXACT_ONLY; - break; - case AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES: - termMatchTypeProto = TermMatchType.Code.PREFIX; - break; - default: - throw new IllegalArgumentException("Invalid indexingType: " + indexingType); - } - indexingConfig.setTermMatchType(termMatchTypeProto); - - // Set tokenizerType - @AppSearchSchema.PropertyConfig.TokenizerType - int tokenizerType = property.getTokenizerType(); - StringIndexingConfig.TokenizerType.Code tokenizerTypeProto = - StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType); - if (tokenizerTypeProto == null) { - throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType); + if (property instanceof AppSearchSchema.StringPropertyConfig) { + AppSearchSchema.StringPropertyConfig stringProperty = + (AppSearchSchema.StringPropertyConfig) property; + StringIndexingConfig stringIndexingConfig = + StringIndexingConfig.newBuilder() + .setTermMatchType( + convertTermMatchTypeToProto(stringProperty.getIndexingType())) + .setTokenizerType( + convertTokenizerTypeToProto(stringProperty.getTokenizerType())) + .build(); + builder.setStringIndexingConfig(stringIndexingConfig); + + } else if (property instanceof AppSearchSchema.DocumentPropertyConfig) { + AppSearchSchema.DocumentPropertyConfig documentProperty = + (AppSearchSchema.DocumentPropertyConfig) property; + builder.setSchemaType(documentProperty.getSchemaType()) + .setDocumentIndexingConfig( + DocumentIndexingConfig.newBuilder() + .setIndexNestedProperties( + documentProperty.isIndexNestedProperties())); } - indexingConfig.setTokenizerType(tokenizerTypeProto); - - // Build! - builder.setStringIndexingConfig(indexingConfig); return builder.build(); } @@ -129,7 +116,8 @@ public final class SchemaToProtoConverter { @NonNull public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) { Preconditions.checkNotNull(proto); - AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType()); + AppSearchSchema.Builder builder = + new AppSearchSchema.Builder(proto.getSchemaType()).setVersion(proto.getVersion()); List<PropertyConfigProto> properties = proto.getPropertiesList(); for (int i = 0; i < properties.size(); i++) { AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i)); @@ -142,39 +130,99 @@ public final class SchemaToProtoConverter { private static AppSearchSchema.PropertyConfig toPropertyConfig( @NonNull PropertyConfigProto proto) { Preconditions.checkNotNull(proto); - AppSearchSchema.PropertyConfig.Builder builder = - new AppSearchSchema.PropertyConfig.Builder(proto.getPropertyName()) - .setDataType(proto.getDataType().getNumber()) + switch (proto.getDataType()) { + case STRING: + return toStringPropertyConfig(proto); + case INT64: + return new AppSearchSchema.Int64PropertyConfig.Builder(proto.getPropertyName()) + .setCardinality(proto.getCardinality().getNumber()) + .build(); + case DOUBLE: + return new AppSearchSchema.DoublePropertyConfig.Builder(proto.getPropertyName()) + .setCardinality(proto.getCardinality().getNumber()) + .build(); + case BOOLEAN: + return new AppSearchSchema.BooleanPropertyConfig.Builder(proto.getPropertyName()) + .setCardinality(proto.getCardinality().getNumber()) + .build(); + case BYTES: + return new AppSearchSchema.BytesPropertyConfig.Builder(proto.getPropertyName()) + .setCardinality(proto.getCardinality().getNumber()) + .build(); + case DOCUMENT: + return toDocumentPropertyConfig(proto); + default: + throw new IllegalArgumentException("Invalid dataType: " + proto.getDataType()); + } + } + + @NonNull + private static AppSearchSchema.StringPropertyConfig toStringPropertyConfig( + @NonNull PropertyConfigProto proto) { + AppSearchSchema.StringPropertyConfig.Builder builder = + new AppSearchSchema.StringPropertyConfig.Builder(proto.getPropertyName()) .setCardinality(proto.getCardinality().getNumber()) .setTokenizerType( proto.getStringIndexingConfig().getTokenizerType().getNumber()); - // Set schema - if (!proto.getSchemaType().isEmpty()) { - builder.setSchemaType(proto.getSchemaType()); - } - // Set indexingType - @AppSearchSchema.PropertyConfig.IndexingType int indexingType; TermMatchType.Code termMatchTypeProto = proto.getStringIndexingConfig().getTermMatchType(); - switch (termMatchTypeProto) { + builder.setIndexingType(convertTermMatchTypeFromProto(termMatchTypeProto)); + + return builder.build(); + } + + @NonNull + private static AppSearchSchema.DocumentPropertyConfig toDocumentPropertyConfig( + @NonNull PropertyConfigProto proto) { + return new AppSearchSchema.DocumentPropertyConfig.Builder(proto.getPropertyName()) + .setCardinality(proto.getCardinality().getNumber()) + .setSchemaType(proto.getSchemaType()) + .setIndexNestedProperties( + proto.getDocumentIndexingConfig().getIndexNestedProperties()) + .build(); + } + + @NonNull + private static TermMatchType.Code convertTermMatchTypeToProto( + @AppSearchSchema.StringPropertyConfig.IndexingType int indexingType) { + switch (indexingType) { + case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE: + return TermMatchType.Code.UNKNOWN; + case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS: + return TermMatchType.Code.EXACT_ONLY; + case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES: + return TermMatchType.Code.PREFIX; + default: + throw new IllegalArgumentException("Invalid indexingType: " + indexingType); + } + } + + @AppSearchSchema.StringPropertyConfig.IndexingType + private static int convertTermMatchTypeFromProto(@NonNull TermMatchType.Code termMatchType) { + switch (termMatchType) { case UNKNOWN: - indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE; - break; + return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; case EXACT_ONLY: - indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS; - break; + return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS; case PREFIX: - indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES; - break; + return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES; default: // Avoid crashing in the 'read' path; we should try to interpret the document to the // extent possible. - Log.w(TAG, "Invalid indexingType: " + termMatchTypeProto.getNumber()); - indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE; + Log.w(TAG, "Invalid indexingType: " + termMatchType.getNumber()); + return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; } - builder.setIndexingType(indexingType); + } - return builder.build(); + @NonNull + private static StringIndexingConfig.TokenizerType.Code convertTokenizerTypeToProto( + @AppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType) { + StringIndexingConfig.TokenizerType.Code tokenizerTypeProto = + StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType); + if (tokenizerTypeProto == null) { + throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType); + } + return tokenizerTypeProto; } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java index 0d7d3e12aa0e..07d50ae1a705 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java @@ -25,10 +25,6 @@ import com.google.android.icing.proto.ResultSpecProto; import com.google.android.icing.proto.ScoringSpecProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.TermMatchType; -import com.google.android.icing.proto.TypePropertyMask; - -import java.util.List; -import java.util.Map; /** * Translates a {@link SearchSpec} into icing search protos. @@ -61,22 +57,17 @@ public final class SearchSpecToProtoConverter { @NonNull public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) { Preconditions.checkNotNull(spec); - ResultSpecProto.Builder builder = - ResultSpecProto.newBuilder() - .setNumPerPage(spec.getResultCountPerPage()) - .setSnippetSpec( - ResultSpecProto.SnippetSpecProto.newBuilder() - .setNumToSnippet(spec.getSnippetCount()) - .setNumMatchesPerProperty(spec.getSnippetCountPerProperty()) - .setMaxWindowBytes(spec.getMaxSnippetSize())); - Map<String, List<String>> projectionTypePropertyPaths = spec.getProjections(); - for (Map.Entry<String, List<String>> e : projectionTypePropertyPaths.entrySet()) { - builder.addTypePropertyMasks( - TypePropertyMask.newBuilder() - .setSchemaType(e.getKey()) - .addAllPaths(e.getValue())); - } - return builder.build(); + return ResultSpecProto.newBuilder() + .setNumPerPage(spec.getResultCountPerPage()) + .setSnippetSpec( + ResultSpecProto.SnippetSpecProto.newBuilder() + .setNumToSnippet(spec.getSnippetCount()) + .setNumMatchesPerProperty(spec.getSnippetCountPerProperty()) + .setMaxWindowBytes(spec.getMaxSnippetSize())) + .addAllTypePropertyMasks( + TypePropertyPathToProtoConverter.toTypePropertyMaskList( + spec.getProjections())) + .build(); } /** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */ @@ -109,6 +100,10 @@ public final class SearchSpecToProtoConverter { return ScoringSpecProto.RankingStrategy.Code.CREATION_TIMESTAMP; case SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE: return ScoringSpecProto.RankingStrategy.Code.RELEVANCE_SCORE; + case SearchSpec.RANKING_STRATEGY_USAGE_COUNT: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT; + case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP; default: throw new IllegalArgumentException( "Invalid result ranking strategy: " + rankingStrategyCode); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java new file mode 100644 index 000000000000..e1e7d46d77ea --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java @@ -0,0 +1,64 @@ +/* + * 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.converter; + +import android.annotation.NonNull; +import android.app.appsearch.SetSchemaResult; + +import com.android.internal.util.Preconditions; + +import com.google.android.icing.proto.SetSchemaResultProto; + +/** + * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResult}. + * + * @hide + */ +public class SetSchemaResultToProtoConverter { + + private SetSchemaResultToProtoConverter() {} + + /** + * Translate a {@link SetSchemaResultProto} into {@link SetSchemaResult}. + * + * @param proto The {@link SetSchemaResultProto} containing results. + * @param prefix The prefix need to removed from schemaTypes + * @return {@link SetSchemaResult} of results. + */ + @NonNull + public static SetSchemaResult toSetSchemaResult( + @NonNull SetSchemaResultProto proto, @NonNull String prefix) { + Preconditions.checkNotNull(proto); + Preconditions.checkNotNull(prefix); + SetSchemaResult.Builder builder = + new SetSchemaResult.Builder() + .setResultCode( + ResultCodeToProtoConverter.toResultCode( + proto.getStatus().getCode())); + + for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) { + builder.addDeletedSchemaType(proto.getDeletedSchemaTypes(i).substring(prefix.length())); + } + + for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) { + builder.addIncompatibleSchemaType( + proto.getIncompatibleSchemaTypes(i).substring(prefix.length())); + } + + return builder.build(); + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java new file mode 100644 index 000000000000..6f6dad207713 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java @@ -0,0 +1,52 @@ +/* + * 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.converter; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import com.google.android.icing.proto.TypePropertyMask; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Translates a <code>Map<String, List<String>></code> into <code>List<TypePropertyMask></code>. + * + * @hide + */ +public final class TypePropertyPathToProtoConverter { + private TypePropertyPathToProtoConverter() {} + + /** Extracts {@link TypePropertyMask} information from a {@link Map}. */ + @NonNull + public static List<TypePropertyMask> toTypePropertyMaskList( + @NonNull Map<String, List<String>> typePropertyPaths) { + Preconditions.checkNotNull(typePropertyPaths); + List<TypePropertyMask> typePropertyMasks = new ArrayList<>(typePropertyPaths.size()); + for (Map.Entry<String, List<String>> e : typePropertyPaths.entrySet()) { + typePropertyMasks.add( + TypePropertyMask.newBuilder() + .setSchemaType(e.getKey()) + .addAllPaths(e.getValue()) + .build()); + } + return typePropertyMasks; + } +} diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 9be30495eb90..277418164210 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -Icd58005ad659b6b3d03f683f8954939175324685 +I3fd4c96bf775c2539d744c416cdbf1d3c9544f03 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 b0478d59f237..f8d0d8052024 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 @@ -28,10 +28,12 @@ import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByUriRequest; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByUriRequest; +import android.app.appsearch.ReportUsageRequest; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchResultsShim; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; +import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; @@ -84,8 +86,8 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @Override @NonNull - public ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request) { - SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); + public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) { + SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create(); mAppSearchSession.setSchema(request, mExecutor, future::set); return Futures.transformAsync(future, this::transformResult, mExecutor); } @@ -129,6 +131,14 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @Override @NonNull + public ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request) { + SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); + mAppSearchSession.reportUsage(request, mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + + @Override + @NonNull public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri( @NonNull RemoveByUriRequest request) { SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create(); @@ -150,6 +160,16 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { mAppSearchSession.close(); } + @Override + @NonNull + public ListenableFuture<Void> maybeFlush() { + SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); + // The data in platform will be flushed by scheduled task. AppSearchSession won't do + // anything extra flush. + future.set(AppSearchResult.newSuccessfulResult(null)); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + private <T> ListenableFuture<T> transformResult( @NonNull AppSearchResult<T> result) throws AppSearchException { if (!result.isSuccess()) { 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 3e819687dae0..e8ea6ef7fe87 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 @@ -69,12 +69,19 @@ public interface AppSearchSessionShim extends Closeable { * {@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 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. + * <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. + * <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type + * and make no deletion. The migrator will migrate documents from it's old schema version + * to the new version. See the migration section below. + * </ul> * * <p>It is a no-op to set the same schema as has been previously set; this is handled * efficiently. @@ -85,13 +92,34 @@ public interface AppSearchSessionShim extends Closeable { * Visibility settings for a schema type do not apply or persist across {@link * SetSchemaRequest}s. * + * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old + * schema. You can save your documents by setting {@link + * android.app.appsearch.AppSearchSchema.Migrator} via the {@link + * SetSchemaRequest.Builder#setMigrator} for each type you want to save. + * + * <p>{@link android.app.appsearch.AppSearchSchema.Migrator#onDowngrade} or {@link + * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} will be triggered if the version + * number of the schema stored in AppSearch is different with the version in the request. + * + * <p>If any error or Exception occurred in the {@link + * android.app.appsearch.AppSearchSchema.Migrator#onDowngrade}, {@link + * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} or {@link + * android.app.appsearch.AppSearchMigrationHelper.Transformer#transform}, the migration will be + * terminated, the setSchema request will be rejected unless the schema changes are + * backwards-compatible, and stored documents won't have any observable changes. + * * @param request The schema update request. - * @return The pending result of performing this operation. + * @return The pending {@link SetSchemaResponse} of performing this operation. Success if the + * the schema has been set and any migrations has been done. Otherwise, the failure {@link + * android.app.appsearch.SetSchemaResponse.MigrationFailure} indicates which document is + * fail to be migrated. + * @see android.app.appsearch.AppSearchSchema.Migrator + * @see android.app.appsearch.AppSearchMigrationHelper.Transformer */ // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are // exposed. @NonNull - ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request); + ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request); /** * Retrieves the schema most recently successfully provided to {@link #setSchema}. @@ -175,6 +203,25 @@ public interface AppSearchSessionShim extends Closeable { SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); /** + * Reports usage of a particular document by URI and namespace. + * + * <p>A usage report represents an event in which a user interacted with or viewed a document. + * + * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency + * metrics for that particular document. These metrics are used for ordering {@link #query} + * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link + * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. + * + * <p>Reporting usage of a document is optional. + * + * @param request The usage reporting request. + * @return The pending result of performing this operation which resolves to {@code null} on + * success. + */ + @NonNull + ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request); + + /** * Removes {@link GenericDocument}s from the index by URI. * * @param request Request containing URIs to be removed. @@ -208,6 +255,17 @@ public interface AppSearchSessionShim extends Closeable { @NonNull String queryExpression, @NonNull SearchSpec searchSpec); /** + * Flush all schema and document updates, additions, and deletes to disk if possible. + * + * @return The pending result of performing this operation. {@link + * android.app.appsearch.exceptions.AppSearchException} with {@link + * AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when + * save to disk. + */ + @NonNull + ListenableFuture<Void> maybeFlush(); + + /** * Closes the {@link AppSearchSessionShim} to persist all schema and document updates, * additions, and deletes to disk. */ diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java index 459fd151ede4..20fb90986f41 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java @@ -25,9 +25,11 @@ import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByUriRequest; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResultsShim; +import android.app.appsearch.SetSchemaResponse; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.Future; public class AppSearchTestUtils { @@ -41,6 +43,15 @@ public class AppSearchTestUtils { return result; } + // TODO(b/151178558) check setSchemaResponse.xxxtypes for the test need to verify. + public static void checkIsSetSchemaResponseSuccess(Future<SetSchemaResponse> future) + throws Exception { + SetSchemaResponse setSchemaResponse = future.get(); + assertWithMessage("SetSchemaResponse not successful.") + .that(setSchemaResponse.isSuccess()) + .isTrue(); + } + public static List<GenericDocument> doGet( AppSearchSessionShim session, String namespace, String... uris) throws Exception { AppSearchBatchResult<String, GenericDocument> result = @@ -59,6 +70,20 @@ public class AppSearchTestUtils { return list; } + public static List<GenericDocument> doGet(AppSearchSessionShim session, GetByUriRequest request) + throws Exception { + AppSearchBatchResult<String, GenericDocument> result = + checkIsBatchResultSuccess(session.getByUri(request)); + Set<String> uris = request.getUris(); + assertThat(result.getSuccesses()).hasSize(uris.size()); + assertThat(result.getFailures()).isEmpty(); + List<GenericDocument> list = new ArrayList<>(uris.size()); + for (String uri : uris) { + list.add(result.getSuccesses().get(uri)); + } + return list; + } + public static List<GenericDocument> convertSearchResultsToDocuments( SearchResultsShim searchResults) throws Exception { List<SearchResult> results = searchResults.getNextPage().get(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING index 484fec31e594..56aa59034056 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING @@ -40,6 +40,13 @@ "options": [ {"include-filter": "com.android.server.job"} ] + }, + { + "name": "CtsHostsideNetworkTests", + "options": [ + {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"}, + {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"} + ] } ] } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index 04d694778e1a..58396eb69d14 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -203,7 +203,7 @@ public final class BackgroundJobsController extends StateController { isActive = (activeState == KNOWN_ACTIVE); } if (isActive && jobStatus.getStandbyBucket() == NEVER_INDEX) { - Slog.wtf(TAG, "App " + packageName + " became active but still in NEVER bucket"); + jobStatus.maybeLogBucketMismatch(); } boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun); didChange |= jobStatus.setUidActive(isActive); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 51525e0c38f5..09dc7d281cb4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -206,6 +206,11 @@ public final class JobStatus { private int standbyBucket; /** + * Whether we've logged an error due to standby bucket mismatch with active uid state. + */ + private boolean mLoggedBucketMismatch; + + /** * Debugging: timestamp if we ever defer this job based on standby bucketing, this * is when we did so. */ @@ -805,6 +810,18 @@ public final class JobStatus { } standbyBucket = newBucket; + mLoggedBucketMismatch = false; + } + + /** + * Log a bucket mismatch if this is the first time for this job. + */ + public void maybeLogBucketMismatch() { + if (!mLoggedBucketMismatch) { + Slog.wtf(TAG, + "App " + getSourcePackageName() + " became active but still in NEVER bucket"); + mLoggedBucketMismatch = true; + } } // Called only by the standby monitoring code diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt index 103bb47fca23..2543a9cad576 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -22,7 +22,6 @@ package android.media { method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedHdrType(@NonNull String); method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedVideoMimeType(@NonNull String); method @NonNull public android.media.ApplicationMediaCapabilities build(); - method @NonNull public android.media.ApplicationMediaCapabilities.Builder setSlowMotionSupported(boolean); } public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException { diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index 24c9e7835ff9..e1a859648ee6 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -56,16 +56,10 @@ import java.util.Set; * <h4>Capability of handling HDR(high dynamic range) video</h4> * There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform, * application will only need to specify individual types they supported. - * - * <h4>Capability of handling Slow Motion video</h4> - * There is no standard format for slow motion yet. If an application indicates support for slow - * motion, it is application's responsibility to parse the slow motion videos using their own parser - * or using support library. */ // TODO(huang): Correct openTypedAssetFileDescriptor with the new API after it is added. // TODO(hkuang): Add a link to seamless transcoding detail when it is published // TODO(hkuang): Add code sample on how to build a capability object with MediaCodecList -// TODO(hkuang): Add the support library page on parsing slow motion video. public final class ApplicationMediaCapabilities implements Parcelable { private static final String TAG = "ApplicationMediaCapabilities"; @@ -260,6 +254,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { /* * Whether handling of slow-motion video is supported + * @hide */ public boolean isSlowMotionSupported() { return mIsSlowMotionSupported; @@ -578,6 +573,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { * If an application indicates support for slow-motion, it is application's responsibility * to parse the slow-motion videos using their own parser or using support library. * @see android.media.MediaFormat#KEY_SLOW_MOTION_MARKERS + * @hide */ @NonNull public Builder setSlowMotionSupported(boolean slowMotionSupported) { diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java index 55c462973c53..0852fdf7466b 100644 --- a/apex/media/framework/java/android/media/MediaTranscodeManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java @@ -1023,10 +1023,13 @@ public final class MediaTranscodeManager { "Source Width and height must be larger than 0"); } - float frameRate = mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE); - if (frameRate <= 0) { - throw new IllegalArgumentException( - "frameRate must be larger than 0"); + float frameRate = 30.0f; // default to 30fps. + if (mSrcVideoFormatHint.containsKey(MediaFormat.KEY_FRAME_RATE)) { + frameRate = mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE); + if (frameRate <= 0) { + throw new IllegalArgumentException( + "frameRate must be larger than 0"); + } } int bitrate = getAVCBitrate(width, height, frameRate); diff --git a/api/Android.bp b/api/Android.bp index 69f03b8d101c..1a58a16f3dc7 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -52,10 +52,7 @@ genrule { dest: "current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk", "win_sdk"], dir: "apistubs/android/public/api", dest: "android.txt", }, @@ -112,6 +109,11 @@ genrule { dir: "api", dest: "removed.txt", }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/public/api", + dest: "removed.txt", + }, ], } @@ -139,10 +141,7 @@ genrule { dest: "system-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk", "win_sdk"], dir: "apistubs/android/system/api", dest: "android.txt", }, @@ -173,6 +172,11 @@ genrule { dir: "api", dest: "system-removed.txt", }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system/api", + dest: "removed.txt", + }, ], visibility: ["//visibility:public"], } @@ -201,10 +205,7 @@ genrule { dest: "module-lib-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk", "win_sdk"], dir: "apistubs/android/module-lib/api", dest: "android.txt", }, @@ -234,6 +235,11 @@ genrule { dir: "api", dest: "module-lib-removed.txt", }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/module-lib/api", + dest: "removed.txt", + }, ], } diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index ebe6199690f7..4e46aa3f42d5 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -174,10 +174,6 @@ public class Am extends BaseCommand { instrument.noWindowAnimation = true; } else if (opt.equals("--no-hidden-api-checks")) { instrument.disableHiddenApiChecks = true; - } else if (opt.equals("--no-test-api-checks")) { - // TODO(satayev): remove this option, only kept for backwards compatibility with - // cached tradefed instance - instrument.disableTestApiChecks = false; } else if (opt.equals("--no-test-api-access")) { instrument.disableTestApiChecks = false; } else if (opt.equals("--no-isolated-storage")) { @@ -200,7 +196,6 @@ public class Am extends BaseCommand { } instrument.componentNameArg = nextArgRequired(); - instrument.run(); } } diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 9088db8c66a1..260c8a47ea3c 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -20,6 +20,7 @@ import android.os.IVoldTaskListener; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.storage.DiskInfo; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; @@ -30,6 +31,8 @@ import java.util.concurrent.CompletableFuture; public final class Sm { private static final String TAG = "Sm"; + private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.sys.vold_app_data_isolation_enabled"; IStorageManager mSm; @@ -254,6 +257,10 @@ public final class Sm { } public void runDisableAppDataIsolation() throws RemoteException { + if (!SystemProperties.getBoolean( + ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { + throw new IllegalStateException("Storage app data isolation is not enabled."); + } final String pkgName = nextArg(); final int pid = Integer.parseInt(nextArg()); final int userId = Integer.parseInt(nextArg()); diff --git a/core/api/current.txt b/core/api/current.txt index 9da670f7da7b..a496828b76bb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10311,6 +10311,7 @@ package android.content { method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyBroadcast(@RequiresPermission android.content.Intent); + method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public void sendStickyBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void sendStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content.Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); @@ -31281,6 +31282,7 @@ package android.os { method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects(); method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects(); method @NonNull public android.os.StrictMode.VmPolicy.Builder detectNonSdkApiUsage(); + method @NonNull public android.os.StrictMode.VmPolicy.Builder detectUnsafeIntentLaunch(); method @NonNull public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets(); method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyDeath(); method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork(); @@ -31289,6 +31291,7 @@ package android.os { method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.StrictMode.OnVmViolationListener); method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyLog(); method @NonNull public android.os.StrictMode.VmPolicy.Builder permitNonSdkApiUsage(); + method @NonNull public android.os.StrictMode.VmPolicy.Builder permitUnsafeIntentLaunch(); method @NonNull public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(Class, int); } @@ -31815,6 +31818,9 @@ package android.os.strictmode { public final class UnbufferedIoViolation extends android.os.strictmode.Violation { } + public final class UnsafeIntentLaunchViolation extends android.os.strictmode.Violation { + } + public final class UntaggedSocketViolation extends android.os.strictmode.Violation { } @@ -36454,6 +36460,9 @@ package android.se.omapi { method @NonNull public String getVersion(); method public boolean isConnected(); method public void shutdown(); + field public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED = "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED"; + field public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME"; + field public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE"; } public static interface SEService.OnConnectedListener { @@ -38197,6 +38206,7 @@ package android.service.textservice { ctor public SpellCheckerService.Session(); method public android.os.Bundle getBundle(); method public String getLocale(); + method public int getSupportedAttributes(); method public void onCancel(); method public void onClose(); method public abstract void onCreate(); @@ -41469,6 +41479,7 @@ 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); @@ -41771,7 +41782,9 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); method public void setSubscriptionOverrideCongested(int, boolean, long); + method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long); method public void setSubscriptionOverrideUnmetered(int, boolean, long); + method public void setSubscriptionOverrideUnmetered(int, boolean, @NonNull int[], long); method public void setSubscriptionPlans(int, @NonNull java.util.List<android.telephony.SubscriptionPlan>); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, @NonNull android.app.PendingIntent); field public static final String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; @@ -41847,6 +41860,7 @@ package android.telephony { public class TelephonyManager { method public boolean canChangeDtmfToneLength(); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearSignalStrengthUpdateRequest(@NonNull android.telephony.SignalStrengthUpdateRequest); method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot(); @@ -41926,7 +41940,7 @@ package android.telephony { method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); method public boolean isConcurrentVoiceAndDataSupported(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed(); - method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled(); + method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); method public boolean isEmergencyNumber(@NonNull String); @@ -41960,6 +41974,7 @@ package android.telephony { method public boolean setOperatorBrandOverride(String); method public boolean setPreferredNetworkTypeToGlobal(); method public void setPreferredOpportunisticDataSubscription(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSignalStrengthUpdateRequest(@NonNull android.telephony.SignalStrengthUpdateRequest); method public void setVisualVoicemailSmsFilterSettings(android.telephony.VisualVoicemailSmsFilterSettings); method public boolean setVoiceMailNumber(String, String); method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); @@ -42625,6 +42640,7 @@ package android.telephony.ims { field public static final int CODE_MEDIA_NO_DATA = 402; // 0x192 field public static final int CODE_MEDIA_UNSPECIFIED = 404; // 0x194 field public static final int CODE_MULTIENDPOINT_NOT_SUPPORTED = 902; // 0x386 + field public static final int CODE_NETWORK_CONGESTION = 1624; // 0x658 field public static final int CODE_NETWORK_DETACH = 1513; // 0x5e9 field public static final int CODE_NETWORK_REJECT = 1504; // 0x5e0 field public static final int CODE_NETWORK_RESP_TIMEOUT = 1503; // 0x5df @@ -51789,7 +51805,8 @@ package android.view.textservice { method @Nullable public android.view.textservice.SpellCheckerSubtype getCurrentSpellCheckerSubtype(boolean); method @Nullable public java.util.List<android.view.textservice.SpellCheckerInfo> getEnabledSpellCheckersList(); method public boolean isSpellCheckerEnabled(); - method public android.view.textservice.SpellCheckerSession newSpellCheckerSession(android.os.Bundle, java.util.Locale, android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean); + method @Nullable public android.view.textservice.SpellCheckerSession newSpellCheckerSession(@Nullable android.os.Bundle, @Nullable java.util.Locale, @NonNull android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean); + method @Nullable public android.view.textservice.SpellCheckerSession newSpellCheckerSession(@Nullable android.os.Bundle, @Nullable java.util.Locale, @NonNull android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean, int); } } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index bf92b34a5657..061dc872ab1e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -136,6 +136,14 @@ package android.media.session { package android.net { + public class ConnectivityManager { + 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 NetworkCapabilities implements android.os.Parcelable { + field public static final int TRANSPORT_TEST = 7; // 0x7 + } + public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; @@ -146,6 +154,22 @@ package android.net { 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); + } + } package android.os { @@ -154,6 +178,10 @@ package android.os { method public final void markVintfStability(); } + public static class Build.VERSION { + field public static final int FIRST_SDK_INT; + } + public interface Parcelable { method public default int getStability(); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2ca07ee61170..21da2fb3a555 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -45,6 +45,7 @@ package android { field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"; field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; + field public static final String BIND_RESUME_ON_REBOOT_SERVICE = "android.permission.BIND_RESUME_ON_REBOOT_SERVICE"; field public static final String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; field public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; @@ -136,6 +137,7 @@ package android { field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY"; field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; + field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS"; field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION"; field public static final String MANAGE_UI_TRANSLATION = "android.permission.MANAGE_UI_TRANSLATION"; field public static final String MANAGE_USB = "android.permission.MANAGE_USB"; @@ -1656,8 +1658,14 @@ package android.app.usage { package android.bluetooth { public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile { + 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 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 + field public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; // 0x0 field public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; // 0x1 @@ -1839,6 +1847,25 @@ package android.bluetooth { field public static final int UUID_BYTES_32_BIT = 4; // 0x4 } + public final class BufferConstraint implements android.os.Parcelable { + ctor public BufferConstraint(int, int, int); + method public int describeContents(); + method public int getDefaultMillis(); + method public int getMaxMillis(); + method public int getMinMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraint> CREATOR; + } + + 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 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; + } + } package android.bluetooth.le { @@ -6820,6 +6847,7 @@ package android.net { 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); @@ -6829,6 +6857,7 @@ package android.net { 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"; @@ -7022,6 +7051,8 @@ package android.net { 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[]); @@ -7032,6 +7063,9 @@ package android.net { 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(); @@ -7118,6 +7152,9 @@ package android.net { method public abstract void onRequestScores(android.net.NetworkKey[]); } + public class NetworkReleasedException extends java.lang.Exception { + } + public class NetworkRequest implements android.os.Parcelable { method @Nullable public String getRequestorPackageName(); method public int getRequestorUid(); @@ -7190,6 +7227,46 @@ package android.net { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); } + public abstract class QosCallback { + ctor public QosCallback(); + method public void onError(@NonNull android.net.QosCallbackException); + method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); + method public void onQosSessionLost(@NonNull android.net.QosSession); + } + + public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { + } + + public final class QosCallbackException extends java.lang.Exception { + } + + public abstract class QosFilter { + method @NonNull public abstract android.net.Network getNetwork(); + } + + public final class QosSession implements android.os.Parcelable { + ctor public QosSession(int, int); + method public int describeContents(); + method public int getSessionId(); + method public int getSessionType(); + method public long getUniqueId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; + field public static final int TYPE_EPS_BEARER = 1; // 0x1 + } + + public interface QosSessionAttributes { + } + + public final class QosSocketInfo implements android.os.Parcelable { + ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; + method public int describeContents(); + method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); + method @NonNull public android.net.Network getNetwork(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + 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); @@ -7235,6 +7312,12 @@ package android.net { 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); @@ -9346,8 +9429,8 @@ package android.service.attestation { public abstract class ImpressionAttestationService extends android.app.Service { ctor public ImpressionAttestationService(); method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); - method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull String, @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String); - method public abstract boolean onVerifyImpressionToken(@NonNull String, @NonNull android.service.attestation.ImpressionToken); + method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String); + method public abstract boolean onVerifyImpressionToken(@NonNull byte[], @NonNull android.service.attestation.ImpressionToken); } public final class ImpressionToken implements android.os.Parcelable { @@ -9885,6 +9968,18 @@ package android.service.resolver { } +package android.service.resumeonreboot { + + public abstract class ResumeOnRebootService extends android.app.Service { + ctor public ResumeOnRebootService(); + method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @NonNull public abstract byte[] onUnwrap(@NonNull byte[]) throws java.io.IOException; + method @NonNull public abstract byte[] onWrap(@NonNull byte[], long) throws java.io.IOException; + field public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; + } + +} + package android.service.search { public abstract class SearchUiService extends android.app.Service { @@ -11831,6 +11926,19 @@ package android.telephony.data { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public final class EpsBearerQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { + method @NonNull public static android.telephony.data.EpsBearerQosSessionAttributes create(@NonNull android.os.Parcel); + method public int describeContents(); + method public long getGuaranteedDownlinkBitRate(); + method public long getGuaranteedUplinkBitRate(); + method public long getMaxDownlinkBitRate(); + method public long getMaxUplinkBitRate(); + method public int getQci(); + method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR; + } + public abstract class QualifiedNetworksService extends android.app.Service { ctor public QualifiedNetworksService(); method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int); @@ -13383,15 +13491,16 @@ package android.view { public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { method public final long getUserActivityTimeout(); + method public boolean isSystemApplicationOverlay(); + method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean); method public final void setUserActivityTimeout(long); field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public static final int FLAG_BLUR_BEHIND = 4; // 0x4 field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000 field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10 - field @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public static final int SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public int backgroundBlurRadius; } - @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags { + @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags { } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1f941436ae8b..a11ee066c0c5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -377,6 +377,8 @@ package android.app { package android.app.admin { public class DevicePolicyManager { + method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); + method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method public void forceUpdateUserSetupComplete(); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); @@ -385,8 +387,27 @@ package android.app.admin { method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); 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_DEVICE_ADMINS") public void setNextOperationSafety(int, boolean); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; + field public static final String ACTION_MANAGED_PROFILE_CREATED = "android.app.action.MANAGED_PROFILE_CREATED"; + field public static final String ACTION_PROVISIONED_MANAGED_DEVICE = "android.app.action.PROVISIONED_MANAGED_DEVICE"; + field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 + field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb + field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd + field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1 + field public static final int CODE_HAS_PAIRED = 8; // 0x8 + field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9 + field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5 + field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7 + field public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc + field public static final int CODE_OK = 0; // 0x0 + field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf + field public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe + field public static final int CODE_SYSTEM_USER = 10; // 0xa + field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2 + field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3 + field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4 field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17 field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5 field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18 @@ -427,6 +448,62 @@ package android.app.admin { field public static final int OPERATION_SWITCH_USER = 2; // 0x2 field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28 field public static final int OPERATION_WIPE_DATA = 8; // 0x8 + field public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; // 0x3 + field public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1; // 0x1 + field public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2; // 0x2 + field public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6; // 0x6 + field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4 + field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7 + field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5 + } + + public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.content.ComponentName getDeviceAdminComponentName(); + method public long getLocalTime(); + method @Nullable public java.util.Locale getLocale(); + method @NonNull public String getOwnerName(); + method @Nullable public String getTimeZone(); + method public boolean isLeaveAllSystemAppsEnabled(); + method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR; + } + + public static final class FullyManagedDeviceProvisioningParams.Builder { + ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build(); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String); + } + + public final class ManagedProfileProvisioningParams implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.accounts.Account getAccountToMigrate(); + method @NonNull public String getOwnerName(); + method @NonNull public android.content.ComponentName getProfileAdminComponentName(); + method @Nullable public String getProfileName(); + method public boolean isKeepAccountMigrated(); + method public boolean isLeaveAllSystemAppsEnabled(); + method public boolean isOrganizationOwnedProvisioning(); + method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR; + } + + public static final class ManagedProfileProvisioningParams.Builder { + ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams build(); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepAccountMigrated(boolean); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String); + } + + public class ProvisioningException extends android.util.AndroidException { + ctor public ProvisioningException(@NonNull Exception, int); + method public int getProvisioningResult(); } public static final class SecurityLog.SecurityEvent implements android.os.Parcelable { @@ -1095,6 +1172,11 @@ package android.media { method public void forceResourceLost(); } + public final class MediaCodec implements android.media.metrics.PlaybackComponent { + method public String getPlaybackId(); + method public void setPlaybackId(@NonNull String); + } + public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint { ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size); ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size); @@ -1103,6 +1185,10 @@ package android.media { method public int getMaxMacroBlocks(); } + public final class MediaDrm implements java.lang.AutoCloseable { + method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]); + } + public final class MediaRoute2Info implements android.os.Parcelable { method @NonNull public String getOriginalId(); } @@ -1165,6 +1251,15 @@ package android.media.audiopolicy { } +package android.media.metrics { + + public interface PlaybackComponent { + method @NonNull public String getPlaybackId(); + method public void setPlaybackId(@NonNull String); + } + +} + package android.media.tv { public final class TvInputManager { @@ -1187,10 +1282,6 @@ package android.media.tv.tuner { package android.net { - public class ConnectivityManager { - method @RequiresPermission(anyOf={"android.permission.MANAGE_TEST_NETWORKS", android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); - } - public class EthernetManager { method public void setIncludeTestInterfaces(boolean); } @@ -1199,31 +1290,10 @@ package android.net { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } - public final class NetworkCapabilities implements android.os.Parcelable { - method public int[] getCapabilities(); - field public static final int TRANSPORT_TEST = 7; // 0x7 - } - public class NetworkStack { method public static void setServiceForTest(@Nullable android.os.IBinder); } - public final class TestNetworkInterface implements android.os.Parcelable { - ctor public TestNetworkInterface(android.os.ParcelFileDescriptor, String); - method public int describeContents(); - method public android.os.ParcelFileDescriptor getFileDescriptor(); - method public String getInterfaceName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR; - } - - public class TestNetworkManager { - method public android.net.TestNetworkInterface createTapInterface(); - method public android.net.TestNetworkInterface createTunInterface(@NonNull android.net.LinkAddress[]); - method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); - method public void teardownTestNetwork(@NonNull android.net.Network); - } - public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d0d5df91a2a3..baf21eda6725 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -199,6 +199,14 @@ public class ActivityOptions { "android.activity.launchTaskDisplayAreaToken"; /** + * The root task token the activity should be launched into. + * @see #setLaunchRootTask(WindowContainerToken) + * @hide + */ + public static final String KEY_LAUNCH_ROOT_TASK_TOKEN = + "android.activity.launchRootTaskToken"; + + /** * The windowing mode the activity should be launched into. * @hide */ @@ -306,7 +314,7 @@ public class ActivityOptions { * @see #setLaunchCookie * @hide */ - private static final String KEY_LAUNCH_COOKIE = "android.activity.launchCookie"; + public static final String KEY_LAUNCH_COOKIE = "android.activity.launchCookie"; /** @hide */ public static final int ANIM_UNDEFINED = -1; @@ -362,6 +370,7 @@ public class ActivityOptions { private int mLaunchDisplayId = INVALID_DISPLAY; private int mCallerDisplayId = INVALID_DISPLAY; private WindowContainerToken mLaunchTaskDisplayArea; + private WindowContainerToken mLaunchRootTask; @WindowConfiguration.WindowingMode private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED; @WindowConfiguration.ActivityType @@ -1050,6 +1059,7 @@ public class ActivityOptions { mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY); mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY); mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN); + mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN); mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED); mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED); mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); @@ -1342,6 +1352,17 @@ public class ActivityOptions { } /** @hide */ + public WindowContainerToken getLaunchRootTask() { + return mLaunchRootTask; + } + + /** @hide */ + public ActivityOptions setLaunchRootTask(WindowContainerToken windowContainerToken) { + mLaunchRootTask = windowContainerToken; + return this; + } + + /** @hide */ public int getLaunchWindowingMode() { return mLaunchWindowingMode; } @@ -1692,6 +1713,9 @@ public class ActivityOptions { if (mLaunchTaskDisplayArea != null) { b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea); } + if (mLaunchRootTask != null) { + b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask); + } if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) { b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bd437f4ce192..e5c34c58e181 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -64,12 +64,14 @@ import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ProviderInfoList; import android.content.pm.ServiceInfo; @@ -346,6 +348,7 @@ public final class ActivityThread extends ClientTransactionHandler { private int mPendingProcessState = PROCESS_STATE_UNKNOWN; ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>(); private int mLastSessionId; + final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>(); @UnsupportedAppUsage final ArrayMap<IBinder, Service> mServices = new ArrayMap<>(); @UnsupportedAppUsage @@ -3412,7 +3415,7 @@ public final class ActivityThread extends ClientTransactionHandler { cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); - r.intent.prepareToEnterProcess(); + r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo)); if (r.state != null) { r.state.setClassLoader(cl); } @@ -3717,7 +3720,7 @@ public final class ActivityThread extends ClientTransactionHandler { for (int i=0; i<N; i++) { ReferrerIntent intent = intents.get(i); intent.setExtrasClassLoader(r.activity.getClassLoader()); - intent.prepareToEnterProcess(); + intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo)); r.activity.mFragments.noteStateNotSaved(); mInstrumentation.callActivityOnNewIntent(r.activity, intent); } @@ -4052,7 +4055,8 @@ public final class ActivityThread extends ClientTransactionHandler { } java.lang.ClassLoader cl = context.getClassLoader(); data.intent.setExtrasClassLoader(cl); - data.intent.prepareToEnterProcess(); + data.intent.prepareToEnterProcess( + isProtectedComponent(data.info) || isProtectedBroadcast(data.intent)); data.setExtrasClassLoader(cl); receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); @@ -4249,6 +4253,7 @@ public final class ActivityThread extends ClientTransactionHandler { service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); + mServicesData.put(data.token, data); mServices.put(data.token, service); try { ActivityManager.getService().serviceDoneExecuting( @@ -4266,13 +4271,14 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleBindService(BindServiceData data) { + CreateServiceData createData = mServicesData.get(data.token); Service s = mServices.get(data.token); if (DEBUG_SERVICE) Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); - data.intent.prepareToEnterProcess(); + data.intent.prepareToEnterProcess(isProtectedComponent(createData.info)); try { if (!data.rebind) { IBinder binder = s.onBind(data.intent); @@ -4297,11 +4303,12 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleUnbindService(BindServiceData data) { + CreateServiceData createData = mServicesData.get(data.token); Service s = mServices.get(data.token); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); - data.intent.prepareToEnterProcess(); + data.intent.prepareToEnterProcess(isProtectedComponent(createData.info)); boolean doRebind = s.onUnbind(data.intent); try { if (doRebind) { @@ -4373,12 +4380,13 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleServiceArgs(ServiceArgsData data) { + CreateServiceData createData = mServicesData.get(data.token); Service s = mServices.get(data.token); if (s != null) { try { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); - data.args.prepareToEnterProcess(); + data.args.prepareToEnterProcess(isProtectedComponent(createData.info)); } int res; if (!data.taskRemoved) { @@ -4407,6 +4415,7 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleStopService(IBinder token) { + mServicesData.remove(token); Service s = mServices.remove(token); if (s != null) { try { @@ -5026,7 +5035,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (ri.mData != null) { ri.mData.setExtrasClassLoader(r.activity.getClassLoader()); - ri.mData.prepareToEnterProcess(); + ri.mData.prepareToEnterProcess(isProtectedComponent(r.activityInfo)); } if (DEBUG_RESULTS) Slog.v(TAG, "Delivering result to activity " + r + " : " + ri); @@ -7739,6 +7748,66 @@ public final class ActivityThread extends ClientTransactionHandler { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } + /** + * Returns whether the provided {@link ActivityInfo} {@code ai} is a protected component. + * + * @see #isProtectedComponent(ComponentInfo, String) + */ + public static boolean isProtectedComponent(@NonNull ActivityInfo ai) { + return isProtectedComponent(ai, ai.permission); + } + + /** + * Returns whether the provided {@link ServiceInfo} {@code si} is a protected component. + * + * @see #isProtectedComponent(ComponentInfo, String) + */ + public static boolean isProtectedComponent(@NonNull ServiceInfo si) { + return isProtectedComponent(si, si.permission); + } + + /** + * Returns whether the provided {@link ComponentInfo} {@code ci} with the specified {@code + * permission} is a protected component. + * + * <p>A component is protected if it is not exported, or if the specified {@code permission} is + * a signature permission. + */ + private static boolean isProtectedComponent(@NonNull ComponentInfo ci, + @Nullable String permission) { + // Bail early when this process isn't looking for violations + if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false; + + // TODO: consider optimizing by having AMS pre-calculate this value + if (!ci.exported) { + return true; + } + if (permission != null) { + try { + PermissionInfo pi = getPermissionManager().getPermissionInfo(permission, + currentOpPackageName(), 0); + return (pi != null) && pi.getProtection() == PermissionInfo.PROTECTION_SIGNATURE; + } catch (RemoteException ignored) { + } + } + return false; + } + + /** + * Returns whether the action within the provided {@code intent} is a protected broadcast. + */ + public static boolean isProtectedBroadcast(@NonNull Intent intent) { + // Bail early when this process isn't looking for violations + if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false; + + // TODO: consider optimizing by having AMS pre-calculate this value + try { + return getPackageManager().isProtectedBroadcast(intent.getAction()); + } catch (RemoteException ignored) { + } + return false; + } + // ------------------ Regular JNI ------------------------ private native void nPurgePendingResources(); private native void nDumpGraphicsInfo(FileDescriptor fd); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 161b7313b893..b85b1861aa02 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -7689,8 +7689,8 @@ public class AppOpsManager { if (code != OP_RECORD_AUDIO) { return false; } - final String voiceRecognitionComponent = Settings.Secure.getString( - context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); + final String voiceRecognitionComponent = Settings.Secure.getStringForUser( + context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE, userId); final String voiceRecognitionServicePackageName = getComponentPackageNameFromString(voiceRecognitionComponent); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index bac502538740..4ddeb8fbfbef 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1469,6 +1469,45 @@ class ContextImpl extends Context { } } + /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Override + @Deprecated + public void sendStickyBroadcast(@NonNull Intent intent, @Nullable Bundle options) { + warnIfCallingFromSystemProcess(); + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.prepareToLeaveProcess(this); + ActivityManager.getService().broadcastIntentWithFeature( + mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, + null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, + false, true, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override @Deprecated public void sendStickyOrderedBroadcast(Intent intent, @@ -1670,7 +1709,9 @@ class ContextImpl extends Context { flags); if (intent != null) { intent.setExtrasClassLoader(getClassLoader()); - intent.prepareToEnterProcess(); + // TODO: determine at registration time if caller is + // protecting themselves with signature permission + intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent)); } return intent; } catch (RemoteException e) { diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e9d63d2bc788..8d1076ea1277 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -139,6 +139,20 @@ public class Instrumentation { } /** + * Returns if it is being called in an instrumentation environment. + * + * @hide + */ + public boolean isInstrumenting() { + // Check if we have an instrumentation context, as init should only get called by + // the system in startup processes that are being instrumented. + if (mInstrContext == null) { + return false; + } + return true; + } + + /** * Called when the instrumentation is starting, before any application code * has been loaded. Usually this will be implemented to simply call * {@link #start} to begin the instrumentation thread, which will then diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 71b28fba6019..856c13c60b35 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -27,8 +27,9 @@ import android.os.Looper; import android.os.Message; /** - * IntentService is a base class for {@link Service}s that handle asynchronous - * requests (expressed as {@link Intent}s) on demand. Clients send requests + * IntentService is an extension of the {@link Service} component class that + * handles asynchronous requests (expressed as {@link Intent}s) on demand. + * Clients send requests * through {@link android.content.Context#startService(Intent)} calls; the * service is started as needed, handles each Intent in turn using a worker * thread, and stops itself when it runs out of work. diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 50853a3bcf9c..c01b5a32b98b 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1617,7 +1617,9 @@ public final class LoadedApk { try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); - intent.prepareToEnterProcess(); + // TODO: determine at registration time if caller is + // protecting themselves with signature permission + intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent)); setExtrasClassLoader(cl); receiver.setPendingResult(this); receiver.onReceive(mContext, intent); diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 6d79e2d3c166..60bfac566879 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -42,8 +42,10 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS # Multiuser per-file *User* = file:/MULTIUSER_OWNERS -# Notification +# Notification, DND, Status bar per-file *Notification* = file:/packages/SystemUI/OWNERS +per-file *Zen* = file:/packages/SystemUI/OWNERS +per-file *StatusBar* = file:/packages/SystemUI/OWNERS # ResourcesManager per-file ResourcesManager = rtmitchell@google.com, toddke@google.com diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 602e9a338feb..6d75d0fe020a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -352,9 +352,20 @@ public final class PendingIntent implements Parcelable { "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent"); } + // TODO(b/178092897) Remove the below instrumentation check and enforce + // the explicit mutability requirement for apps under instrumentation. + ActivityThread thread = ActivityThread.currentActivityThread(); + Instrumentation mInstrumentation = thread.getInstrumentation(); + if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) && !flagImmutableSet && !flagMutableSet) { - Log.e(TAG, msg); + + //TODO(b/178065720) Remove check for chrome and enforce this requirement + if (packageName.equals("com.android.chrome") || mInstrumentation.isInstrumenting()) { + Log.e(TAG, msg); + } else { + throw new IllegalArgumentException(msg); + } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7e8fb91d362f..530cfd522a4f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -88,6 +88,7 @@ import android.telephony.data.ApnSetting; import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.NetworkUtilsInternal; @@ -148,6 +149,7 @@ import java.util.concurrent.Executor; */ @SystemService(Context.DEVICE_POLICY_SERVICE) @RequiresFeature(PackageManager.FEATURE_DEVICE_ADMIN) +@SuppressLint("UseIcu") public class DevicePolicyManager { private static String TAG = "DevicePolicyManager"; @@ -1992,6 +1994,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_OK = 0; /** @@ -2003,6 +2006,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_HAS_DEVICE_OWNER = 1; /** @@ -2014,6 +2018,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_USER_HAS_PROFILE_OWNER = 2; /** @@ -2024,6 +2029,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_USER_NOT_RUNNING = 3; /** @@ -2035,6 +2041,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_USER_SETUP_COMPLETED = 4; /** @@ -2042,6 +2049,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_NONSYSTEM_USER_EXISTS = 5; /** @@ -2049,6 +2057,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; /** @@ -2059,6 +2068,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_NOT_SYSTEM_USER = 7; /** @@ -2070,6 +2080,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_HAS_PAIRED = 8; /** @@ -2081,6 +2092,7 @@ public class DevicePolicyManager { * @see {@link PackageManager#FEATURE_MANAGED_USERS} * @hide */ + @TestApi public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; /** @@ -2092,6 +2104,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_SYSTEM_USER = 10; /** @@ -2102,6 +2115,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; /** @@ -2114,6 +2128,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; /** @@ -2121,11 +2136,12 @@ public class DevicePolicyManager { * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and - * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do no support device + * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do not support device * admins. * * @hide */ + @TestApi public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; /** @@ -2137,9 +2153,21 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** + * Result code for {@link #checkProvisioningPreCondition}. + * + * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and + * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support provisioning. + * + * @hide + */ + @TestApi + public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; + + /** * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre * conditions. * @@ -2151,11 +2179,94 @@ public class DevicePolicyManager { CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, + CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS }) public @interface ProvisioningPreCondition {} /** + * Service-specific error code for {@link #provisionFullyManagedDevice} and + * {@link #createAndProvisionManagedProfile}: + * Indicates the call to {@link #checkProvisioningPreCondition} returned an error code. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1; + + /** + * Service-specific error code for {@link #createAndProvisionManagedProfile}: + * Indicates the call to {@link UserManager#createProfileForUserEvenWhenDisallowed} + * returned {@code null}. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2; + + /** + * Service-specific error code for {@link #createAndProvisionManagedProfile}: + * Indicates the call to {@link PackageManager#installExistingPackageAsUser} has failed. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; + + /** + * Service-specific error code for {@link #createAndProvisionManagedProfile}: + * Indicates the call to {@link #setProfileOwner} returned {@code false}. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; + + /** + * Service-specific error code for {@link #createAndProvisionManagedProfile}: + * Indicates that starting the newly created profile has failed. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; + + /** + * Service-specific error code for {@link #provisionFullyManagedDevice}: + * Indicates that removing the non required apps have failed. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6; + + /** + * Service-specific error code for {@link #provisionFullyManagedDevice}: + * Indicates the call to {@link #setDeviceOwner} returned {@code false}. + * + * @hide + */ + @TestApi + public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; + + /** + * Service-specific error codes for {@link #createAndProvisionManagedProfile} and + * {@link #provisionFullyManagedDevice} indicating all the errors during provisioning. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "PROVISIONING_RESULT_" }, value = { + PROVISIONING_RESULT_PRE_CONDITION_FAILED, PROVISIONING_RESULT_PROFILE_CREATION_FAILED, + PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED, + PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED, + PROVISIONING_RESULT_STARTING_PROFILE_FAILED, + PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED, + PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED + }) + public @interface ProvisioningResult {} + + /** * Disable all configurable SystemUI features during LockTask mode. This includes, * <ul> * <li>system info area in the status bar (connectivity icons, clock, etc.) @@ -5215,30 +5326,10 @@ public class DevicePolicyManager { if (!proxySpec.type().equals(Proxy.Type.HTTP)) { throw new IllegalArgumentException(); } - InetSocketAddress sa = (InetSocketAddress)proxySpec.address(); - String hostName = sa.getHostName(); - int port = sa.getPort(); - StringBuilder hostBuilder = new StringBuilder(); - hostSpec = hostBuilder.append(hostName) - .append(":").append(Integer.toString(port)).toString(); - if (exclusionList == null) { - exclSpec = ""; - } else { - StringBuilder listBuilder = new StringBuilder(); - boolean firstDomain = true; - for (String exclDomain : exclusionList) { - if (!firstDomain) { - listBuilder = listBuilder.append(","); - } else { - firstDomain = false; - } - listBuilder = listBuilder.append(exclDomain.trim()); - } - exclSpec = listBuilder.toString(); - } - if (android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec) - != android.net.Proxy.PROXY_VALID) - throw new IllegalArgumentException(); + final Pair<String, String> proxyParams = + getProxyParameters(proxySpec, exclusionList); + hostSpec = proxyParams.first; + exclSpec = proxyParams.second; } return mService.setGlobalProxy(admin, hostSpec, exclSpec); } catch (RemoteException e) { @@ -5249,6 +5340,41 @@ public class DevicePolicyManager { } /** + * Build HTTP proxy parameters for {@link IDevicePolicyManager#setGlobalProxy}. + * @throws IllegalArgumentException Invalid proxySpec + * @hide + */ + @VisibleForTesting + public Pair<String, String> getProxyParameters(Proxy proxySpec, List<String> exclusionList) { + InetSocketAddress sa = (InetSocketAddress) proxySpec.address(); + String hostName = sa.getHostName(); + int port = sa.getPort(); + StringBuilder hostBuilder = new StringBuilder(); + final String hostSpec = hostBuilder.append(hostName) + .append(":").append(Integer.toString(port)).toString(); + final String exclSpec; + if (exclusionList == null) { + exclSpec = ""; + } else { + StringBuilder listBuilder = new StringBuilder(); + boolean firstDomain = true; + for (String exclDomain : exclusionList) { + if (!firstDomain) { + listBuilder = listBuilder.append(","); + } else { + firstDomain = false; + } + listBuilder = listBuilder.append(exclDomain.trim()); + } + exclSpec = listBuilder.toString(); + } + if (android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec) + != android.net.Proxy.PROXY_VALID) throw new IllegalArgumentException(); + + return new Pair<>(hostSpec, exclSpec); + } + + /** * Set a network-independent global HTTP proxy. This is not normally what you want for typical * HTTP proxies - they are generally network dependent. However if you're doing something * unusual like general internal filtering this may be useful. On a private network where the @@ -5383,6 +5509,26 @@ public class DevicePolicyManager { "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER"; /** + * Broadcast action: notify managed provisioning that the device has been provisioned. + * + * @hide + */ + @TestApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PROVISIONED_MANAGED_DEVICE = + "android.app.action.PROVISIONED_MANAGED_DEVICE"; + + /** + * Broadcast action: notify managed provisioning that a new managed profile is created. + * + * @hide + */ + @TestApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MANAGED_PROFILE_CREATED = + "android.app.action.MANAGED_PROFILE_CREATED"; + + /** * Widgets are enabled in keyguard */ public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; @@ -10598,8 +10744,9 @@ public class DevicePolicyManager { * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed. * @hide */ + @TestApi public @ProvisioningPreCondition int checkProvisioningPreCondition( - String action, @NonNull String packageName) { + @Nullable String action, @NonNull String packageName) { try { return mService.checkProvisioningPreCondition(action, packageName); } catch (RemoteException re) { @@ -13101,4 +13248,70 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Creates and provisions a managed profile and sets the + * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile + * owner. + * + * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK} + * before calling this method. + * + * @param provisioningParams Params required to provision a managed profile, + * see {@link ManagedProfileProvisioningParams}. + * @return The {@link UserHandle} of the created profile or {@code null} if the service is + * not available. + * @throws SecurityException if the caller does not hold + * {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}. + * @throws ProvisioningException if an error occurred during provisioning. + * @hide + */ + @Nullable + @TestApi + public UserHandle createAndProvisionManagedProfile( + @NonNull ManagedProfileProvisioningParams provisioningParams) + throws ProvisioningException { + if (mService == null) { + return null; + } + try { + return mService.createAndProvisionManagedProfile(provisioningParams); + } catch (ServiceSpecificException e) { + throw new ProvisioningException(e, e.errorCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Provisions a managed device and sets the {@code deviceAdminComponentName} as the device + * owner. + * + * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK} + * before calling this method. + * + * @param provisioningParams Params required to provision a fully managed device, + * see {@link FullyManagedDeviceProvisioningParams}. + * + * @throws SecurityException if the caller does not hold + * {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}. + * @throws ProvisioningException if an error occurred during provisioning. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void provisionFullyManagedDevice( + @NonNull FullyManagedDeviceProvisioningParams provisioningParams) + throws ProvisioningException { + if (mService != null) { + try { + mService.provisionFullyManagedDevice(provisioningParams); + } catch (ServiceSpecificException e) { + throw new ProvisioningException(e, e.errorCode); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.aidl b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.aidl new file mode 100644 index 000000000000..8a9cbba019e8 --- /dev/null +++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.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.app.admin; + +parcelable FullyManagedDeviceProvisioningParams; diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java new file mode 100644 index 000000000000..83af0195ddba --- /dev/null +++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.TestApi; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Locale; + +/** + * Params required to provision a fully managed device, see + * {@link DevicePolicyManager#provisionFullyManagedDevice}. + * @hide + */ +@TestApi +public final class FullyManagedDeviceProvisioningParams implements Parcelable { + @NonNull private final ComponentName mDeviceAdminComponentName; + @NonNull private final String mOwnerName; + private final boolean mLeaveAllSystemAppsEnabled; + @Nullable private final String mTimeZone; + private final long mLocalTime; + @SuppressLint("UseIcu") + @Nullable private final Locale mLocale; + + private FullyManagedDeviceProvisioningParams( + @NonNull ComponentName deviceAdminComponentName, + @NonNull String ownerName, + boolean leaveAllSystemAppsEnabled, + @Nullable String timeZone, + long localTime, + @Nullable @SuppressLint("UseIcu") Locale locale) { + this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName); + this.mOwnerName = requireNonNull(ownerName); + this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; + this.mTimeZone = timeZone; + this.mLocalTime = localTime; + this.mLocale = locale; + } + + private FullyManagedDeviceProvisioningParams( + @NonNull ComponentName deviceAdminComponentName, + @NonNull String ownerName, + boolean leaveAllSystemAppsEnabled, + @Nullable String timeZone, + long localTime, + @Nullable String localeStr) { + this(deviceAdminComponentName, + ownerName, + leaveAllSystemAppsEnabled, + timeZone, + localTime, + getLocale(localeStr)); + } + + @Nullable + private static Locale getLocale(String localeStr) { + return localeStr == null ? null : Locale.forLanguageTag(localeStr); + } + + @NonNull + public ComponentName getDeviceAdminComponentName() { + return mDeviceAdminComponentName; + } + + @NonNull + public String getOwnerName() { + return mOwnerName; + } + + public boolean isLeaveAllSystemAppsEnabled() { + return mLeaveAllSystemAppsEnabled; + } + + @Nullable + public String getTimeZone() { + return mTimeZone; + } + + public long getLocalTime() { + return mLocalTime; + } + + @Nullable + public @SuppressLint("UseIcu") Locale getLocale() { + return mLocale; + } + + /** + * Builder class for {@link FullyManagedDeviceProvisioningParams} objects. + */ + public static final class Builder { + @NonNull private final ComponentName mDeviceAdminComponentName; + @NonNull private final String mOwnerName; + private boolean mLeaveAllSystemAppsEnabled; + @Nullable private String mTimeZone; + private long mLocalTime; + @SuppressLint("UseIcu") + @Nullable private Locale mLocale; + + /** + * Initialize a new {@link Builder} to construct a + * {@link FullyManagedDeviceProvisioningParams}. + * <p> + * See {@link DevicePolicyManager#provisionFullyManagedDevice} + * + * @param deviceAdminComponentName The admin {@link ComponentName} to be set as the device + * owner. + * @param ownerName The name of the device owner. + * + * @throws NullPointerException if {@code deviceAdminComponentName} or + * {@code ownerName} are null. + */ + public Builder( + @NonNull ComponentName deviceAdminComponentName, @NonNull String ownerName) { + this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName); + this.mOwnerName = requireNonNull(ownerName); + } + + /** + * Sets whether non-required system apps should be installed on + * the created profile when + * {@link DevicePolicyManager#provisionFullyManagedDevice} + * is called. Defaults to {@code false} if not set. + */ + @NonNull + public Builder setLeaveAllSystemAppsEnabled(boolean leaveAllSystemAppsEnabled) { + this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; + return this; + } + + /** + * Sets {@code timeZone} on the device. If not set or set to {@code null}, + * {@link DevicePolicyManager#provisionFullyManagedDevice} will not set a timezone + */ + @NonNull + public Builder setTimeZone(@Nullable String timeZone) { + this.mTimeZone = timeZone; + return this; + } + + /** + * Sets {@code localTime} on the device, If not set or set to + * {@code 0}, {@link DevicePolicyManager#provisionFullyManagedDevice} will not set a + * local time. + */ + @NonNull + public Builder setLocalTime(long localTime) { + this.mLocalTime = localTime; + return this; + } + + /** + * Sets {@link Locale} on the device, If not set or set to {@code null}, + * {@link DevicePolicyManager#provisionFullyManagedDevice} will not set a locale. + */ + @NonNull + public Builder setLocale(@SuppressLint("UseIcu") @Nullable Locale locale) { + this.mLocale = locale; + return this; + } + + /** + * Combines all of the attributes that have been set on this {@code Builder} + * + * @return a new {@link FullyManagedDeviceProvisioningParams} object. + */ + @NonNull + public FullyManagedDeviceProvisioningParams build() { + return new FullyManagedDeviceProvisioningParams( + mDeviceAdminComponentName, + mOwnerName, + mLeaveAllSystemAppsEnabled, + mTimeZone, + mLocalTime, + mLocale); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "FullyManagedDeviceProvisioningParams{" + + "mDeviceAdminComponentName=" + mDeviceAdminComponentName + + ", mOwnerName=" + mOwnerName + + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled + + ", mTimeZone=" + (mTimeZone == null ? "null" : mTimeZone) + + ", mLocalTime=" + mLocalTime + + ", mLocale=" + (mLocale == null ? "null" : mLocale) + + '}'; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) { + dest.writeTypedObject(mDeviceAdminComponentName, flags); + dest.writeString(mOwnerName); + dest.writeBoolean(mLeaveAllSystemAppsEnabled); + dest.writeString(mTimeZone); + dest.writeLong(mLocalTime); + dest.writeString(mLocale == null ? null : mLocale.toLanguageTag()); + } + + @NonNull + public static final Creator<FullyManagedDeviceProvisioningParams> CREATOR = + new Creator<FullyManagedDeviceProvisioningParams>() { + @Override + public FullyManagedDeviceProvisioningParams createFromParcel(Parcel in) { + ComponentName componentName = in.readTypedObject(ComponentName.CREATOR); + String ownerName = in.readString(); + boolean leaveAllSystemAppsEnabled = in.readBoolean(); + String timeZone = in.readString(); + long localtime = in.readLong(); + String locale = in.readString(); + + return new FullyManagedDeviceProvisioningParams( + componentName, + ownerName, + leaveAllSystemAppsEnabled, + timeZone, + localtime, + locale); + } + + @Override + public FullyManagedDeviceProvisioningParams[] newArray(int size) { + return new FullyManagedDeviceProvisioningParams[size]; + } + }; +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 8f84bfe0d28a..3765a67b9c8a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -25,6 +25,8 @@ import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.admin.PasswordMetrics; import android.app.admin.FactoryResetProtectionPolicy; +import android.app.admin.ManagedProfileProvisioningParams; +import android.app.admin.FullyManagedDeviceProvisioningParams; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -493,4 +495,7 @@ interface IDevicePolicyManager { String getEnrollmentSpecificId(String callerPackage); void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId); + + UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams); + void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams); } diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.aidl b/core/java/android/app/admin/ManagedProfileProvisioningParams.aidl new file mode 100644 index 000000000000..a6fae91e05c8 --- /dev/null +++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.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.app.admin; + +parcelable ManagedProfileProvisioningParams; diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java new file mode 100644 index 000000000000..5fe63d142021 --- /dev/null +++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import static java.util.Objects.requireNonNull; + +import android.accounts.Account; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Params required to provision a managed profile, see + * {@link DevicePolicyManager#createAndProvisionManagedProfile}. + * + * @hide + */ +@TestApi +public final class ManagedProfileProvisioningParams implements Parcelable { + @NonNull private final ComponentName mProfileAdminComponentName; + @NonNull private final String mOwnerName; + @Nullable private final String mProfileName; + @Nullable private final Account mAccountToMigrate; + private final boolean mLeaveAllSystemAppsEnabled; + private final boolean mOrganizationOwnedProvisioning; + private final boolean mKeepAccountMigrated; + + + private ManagedProfileProvisioningParams( + @NonNull ComponentName profileAdminComponentName, + @NonNull String ownerName, + @Nullable String profileName, + @Nullable Account accountToMigrate, + boolean leaveAllSystemAppsEnabled, + boolean organizationOwnedProvisioning, + boolean keepAccountMigrated) { + this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName); + this.mOwnerName = requireNonNull(ownerName); + this.mProfileName = profileName; + this.mAccountToMigrate = accountToMigrate; + this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; + this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning; + this.mKeepAccountMigrated = keepAccountMigrated; + } + + @NonNull + public ComponentName getProfileAdminComponentName() { + return mProfileAdminComponentName; + } + + @NonNull + public String getOwnerName() { + return mOwnerName; + } + + @Nullable + public String getProfileName() { + return mProfileName; + } + + @Nullable + public Account getAccountToMigrate() { + return mAccountToMigrate; + } + + public boolean isLeaveAllSystemAppsEnabled() { + return mLeaveAllSystemAppsEnabled; + } + + public boolean isOrganizationOwnedProvisioning() { + return mOrganizationOwnedProvisioning; + } + + public boolean isKeepAccountMigrated() { + return mKeepAccountMigrated; + } + + /** + * Builder class for {@link ManagedProfileProvisioningParams} objects. + */ + public static final class Builder { + @NonNull private final ComponentName mProfileAdminComponentName; + @NonNull private final String mOwnerName; + @Nullable private String mProfileName; + @Nullable private Account mAccountToMigrate; + private boolean mLeaveAllSystemAppsEnabled; + private boolean mOrganizationOwnedProvisioning; + private boolean mKeepAccountMigrated; + + /** + * Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}. + * <p> + * See {@link DevicePolicyManager#createAndProvisionManagedProfile} + * + * @param profileAdminComponentName The admin {@link ComponentName} to be set as the profile + * owner. + * @param ownerName The name of the profile owner. + * + * @throws NullPointerException if {@code profileAdminComponentName} or + * {@code ownerName} are null. + */ + public Builder( + @NonNull ComponentName profileAdminComponentName, @NonNull String ownerName) { + requireNonNull(profileAdminComponentName); + requireNonNull(ownerName); + this.mProfileAdminComponentName = profileAdminComponentName; + this.mOwnerName = ownerName; + } + + /** + * Sets the profile name of the created profile when + * {@link DevicePolicyManager#createAndProvisionManagedProfile} is called. Defaults to + * {@code null} if not set. + */ + @NonNull + public Builder setProfileName(@Nullable String profileName) { + this.mProfileName = profileName; + return this; + } + + /** + * Sets the {@link Account} to migrate from the parent profile to the created profile when + * {@link DevicePolicyManager#createAndProvisionManagedProfile} is called. If not set, or + * set to {@code null}, no accounts will be migrated. + */ + @NonNull + public Builder setAccountToMigrate(@Nullable Account accountToMigrate) { + this.mAccountToMigrate = accountToMigrate; + return this; + } + + /** + * Sets whether non-required system apps should be installed on + * the created profile when {@link DevicePolicyManager#createAndProvisionManagedProfile} + * is called. Defaults to {@code false} if not set. + */ + @NonNull + public Builder setLeaveAllSystemAppsEnabled(boolean leaveAllSystemAppsEnabled) { + this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; + return this; + } + + /** + * Sets if this device is owned by an organization. Defaults to {@code false} + * if not set. + */ + @NonNull + public Builder setOrganizationOwnedProvisioning(boolean organizationOwnedProvisioning) { + this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning; + return this; + } + + /** + * Sets whether to keep the account on the parent profile during account migration. + * Defaults to {@code false}. + */ + @NonNull + public Builder setKeepAccountMigrated(boolean keepAccountMigrated) { + this.mKeepAccountMigrated = keepAccountMigrated; + return this; + } + + /** + * Combines all of the attributes that have been set on this {@code Builder}. + * + * @return a new {@link ManagedProfileProvisioningParams} object. + */ + @NonNull + public ManagedProfileProvisioningParams build() { + return new ManagedProfileProvisioningParams( + mProfileAdminComponentName, + mOwnerName, + mProfileName, + mAccountToMigrate, + mLeaveAllSystemAppsEnabled, + mOrganizationOwnedProvisioning, + mKeepAccountMigrated); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "ManagedProfileProvisioningParams{" + + "mProfileAdminComponentName=" + mProfileAdminComponentName + + ", mOwnerName=" + mOwnerName + + ", mProfileName=" + (mProfileName == null ? "null" : mProfileName) + + ", mAccountToMigrate=" + (mAccountToMigrate == null ? "null" : mAccountToMigrate) + + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled + + ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning + + ", mKeepAccountMigrated=" + mKeepAccountMigrated + + '}'; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) { + dest.writeTypedObject(mProfileAdminComponentName, flags); + dest.writeString(mOwnerName); + dest.writeString(mProfileName); + dest.writeTypedObject(mAccountToMigrate, flags); + dest.writeBoolean(mLeaveAllSystemAppsEnabled); + dest.writeBoolean(mOrganizationOwnedProvisioning); + dest.writeBoolean(mKeepAccountMigrated); + } + + public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR = + new Creator<ManagedProfileProvisioningParams>() { + @Override + public ManagedProfileProvisioningParams createFromParcel(Parcel in) { + ComponentName componentName = in.readTypedObject(ComponentName.CREATOR); + String ownerName = in.readString(); + String profileName = in.readString(); + Account account = in.readTypedObject(Account.CREATOR); + boolean leaveAllSystemAppsEnabled = in.readBoolean(); + boolean organizationOwnedProvisioning = in.readBoolean(); + boolean keepAccountMigrated = in.readBoolean(); + + return new ManagedProfileProvisioningParams( + componentName, + ownerName, + profileName, + account, + leaveAllSystemAppsEnabled, + organizationOwnedProvisioning, + keepAccountMigrated); + } + + @Override + public ManagedProfileProvisioningParams[] newArray(int size) { + return new ManagedProfileProvisioningParams[size]; + } + }; +} diff --git a/core/java/android/app/admin/ProvisioningException.java b/core/java/android/app/admin/ProvisioningException.java new file mode 100644 index 000000000000..639859bc3dea --- /dev/null +++ b/core/java/android/app/admin/ProvisioningException.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 android.app.admin; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.util.AndroidException; + +/** + * Thrown to indicate a failure during {@link DevicePolicyManager#provisionFullyManagedDevice} and + * {@link DevicePolicyManager#createAndProvisionManagedProfile}. + * + * @hide + * + */ +@TestApi +public class ProvisioningException extends AndroidException { + private final @DevicePolicyManager.ProvisioningResult int mProvisioningResult; + + public ProvisioningException(@NonNull Exception cause, + @DevicePolicyManager.ProvisioningResult int provisioningResult) { + super(cause); + mProvisioningResult = provisioningResult; + } + + public @DevicePolicyManager.ProvisioningResult int getProvisioningResult() { + return mProvisioningResult; + } +} diff --git a/core/java/android/appwidget/OWNERS b/core/java/android/appwidget/OWNERS new file mode 100644 index 000000000000..439df4b86cf0 --- /dev/null +++ b/core/java/android/appwidget/OWNERS @@ -0,0 +1,3 @@ +pinyaoting@google.com +suprabh@google.com +sunnygoyal@google.com diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 15daf1c59d1a..cd91aa9b16b7 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -225,6 +225,39 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; + /** @hide */ + @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = { + DYNAMIC_BUFFER_SUPPORT_NONE, + DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD, + DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** + * Indicates the supported type of Dynamic Audio Buffer is not supported. + * + * @hide + */ + @SystemApi + public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; + + /** + * Indicates the supported type of Dynamic Audio Buffer is A2DP offload. + * + * @hide + */ + @SystemApi + public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; + + /** + * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding. + * + * @hide + */ + @SystemApi + public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; + private BluetoothAdapter mAdapter; private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", @@ -845,6 +878,87 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** + * Get the supported type of the Dynamic Audio Buffer. + * <p>Possible return values are + * {@link #DYNAMIC_BUFFER_SUPPORT_NONE}, + * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD}, + * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}. + * + * @return supported type of Dynamic Audio Buffer feature + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public @Type int getDynamicBufferSupport() { + if (VDBG) log("getDynamicBufferSupport()"); + try { + final IBluetoothA2dp service = getService(); + if (service != null && isEnabled()) { + return service.getDynamicBufferSupport(); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return DYNAMIC_BUFFER_SUPPORT_NONE; + } catch (RemoteException e) { + Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e); + return DYNAMIC_BUFFER_SUPPORT_NONE; + } + } + + /** + * Return the record of {@link BufferConstraints} object that + * has the default/maximum/minimum audio buffer. This can be used to inform what the controller + * has support for the audio buffer. + * + * @return a record with {@link BufferConstraints} or null if report is unavailable + * or unsupported + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public @Nullable BufferConstraints getBufferConstraints() { + if (VDBG) log("getBufferConstraints()"); + try { + final IBluetoothA2dp service = getService(); + if (service != null && isEnabled()) { + return service.getBufferConstraints(); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "", e); + return null; + } + } + + /** + * Set Dynamic Audio Buffer Size. + * + * @param codec audio codec + * @param value buffer millis + * @return true to indicate success, or false on immediate error + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setBufferMillis(@BluetoothCodecConfig.SourceCodecType int codec, int value) { + if (VDBG) log("setBufferMillis(" + codec + ", " + value + ")"); + try { + final IBluetoothA2dp service = getService(); + if (service != null && isEnabled()) { + return service.setBufferMillis(codec, value); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** * Helper for converting a state to a string. * * For debug use only - strings are not internationalized. diff --git a/core/java/android/bluetooth/BufferConstraint.java b/core/java/android/bluetooth/BufferConstraint.java new file mode 100644 index 000000000000..cbffc788c35d --- /dev/null +++ b/core/java/android/bluetooth/BufferConstraint.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Stores a codec's constraints on buffering length in milliseconds. + * + * {@hide} + */ +@SystemApi +public final class BufferConstraint implements Parcelable { + + private static final String TAG = "BufferConstraint"; + private int mDefaultMillis; + private int mMaxMillis; + private int mMinMillis; + + public BufferConstraint(int defaultMillis, int maxMillis, + int minMillis) { + mDefaultMillis = defaultMillis; + mMaxMillis = maxMillis; + mMinMillis = minMillis; + } + + BufferConstraint(Parcel in) { + mDefaultMillis = in.readInt(); + mMaxMillis = in.readInt(); + mMinMillis = in.readInt(); + } + + public static final @NonNull Parcelable.Creator<BufferConstraint> CREATOR = + new Parcelable.Creator<BufferConstraint>() { + public BufferConstraint createFromParcel(Parcel in) { + return new BufferConstraint(in); + } + + public BufferConstraint[] newArray(int size) { + return new BufferConstraint[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mDefaultMillis); + out.writeInt(mMaxMillis); + out.writeInt(mMinMillis); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Get the default buffer millis + * + * @return default buffer millis + * @hide + */ + @SystemApi + public int getDefaultMillis() { + return mDefaultMillis; + } + + /** + * Get the maximum buffer millis + * + * @return maximum buffer millis + * @hide + */ + @SystemApi + public int getMaxMillis() { + return mMaxMillis; + } + + /** + * Get the minimum buffer millis + * + * @return minimum buffer millis + * @hide + */ + @SystemApi + public int getMinMillis() { + return mMinMillis; + } +} diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java new file mode 100644 index 000000000000..7e5ec1e78435 --- /dev/null +++ b/core/java/android/bluetooth/BufferConstraints.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A parcelable collection of buffer constraints by codec type. + * + * {@hide} + */ +@SystemApi +public final class BufferConstraints implements Parcelable { + public static final int BUFFER_CODEC_MAX_NUM = 32; + + private static final String TAG = "BufferConstraints"; + + private Map<Integer, BufferConstraint> mBufferConstraints; + private List<BufferConstraint> mBufferConstraintList; + + public BufferConstraints(@NonNull List<BufferConstraint> + bufferConstraintList) { + + mBufferConstraintList = new ArrayList<BufferConstraint>(bufferConstraintList); + mBufferConstraints = new HashMap<Integer, BufferConstraint>(); + for (int i = 0; i < BUFFER_CODEC_MAX_NUM; i++) { + mBufferConstraints.put(i, bufferConstraintList.get(i)); + } + } + + BufferConstraints(Parcel in) { + mBufferConstraintList = new ArrayList<BufferConstraint>(); + mBufferConstraints = new HashMap<Integer, BufferConstraint>(); + in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader()); + for (int i = 0; i < mBufferConstraintList.size(); i++) { + mBufferConstraints.put(i, mBufferConstraintList.get(i)); + } + } + + public static final @NonNull Parcelable.Creator<BufferConstraints> CREATOR = + new Parcelable.Creator<BufferConstraints>() { + public BufferConstraints createFromParcel(Parcel in) { + return new BufferConstraints(in); + } + + public BufferConstraints[] newArray(int size) { + return new BufferConstraints[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeList(mBufferConstraintList); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Get the buffer constraints by codec type. + * + * @param codec Audio codec + * @return buffer constraints by codec type. + * @hide + */ + @SystemApi + public @Nullable BufferConstraint getCodec(@BluetoothCodecConfig.SourceCodecType int codec) { + return mBufferConstraints.get(codec); + } +} diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index c4d98671a598..018863774184 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -1008,7 +1008,9 @@ public class ClipData implements Parcelable { for (int i = 0; i < size; i++) { final Item item = mItems.get(i); if (item.mIntent != null) { - item.mIntent.prepareToEnterProcess(); + // We can't recursively claim that this data is from a protected + // component, since it may have been filled in by a malicious app + item.mIntent.prepareToEnterProcess(false); } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 219014076c31..e4a81cfadfe0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2625,6 +2625,36 @@ public abstract class Context { public abstract void sendStickyBroadcast(@RequiresPermission Intent intent); /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) + public void sendStickyBroadcast(@RequiresPermission @NonNull Intent intent, + @Nullable Bundle options) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * <p>Version of {@link #sendStickyBroadcast} that allows you to * receive data back from the broadcast. This is accomplished by * supplying your own BroadcastReceiver when calling, which will be diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 078fa0de9251..c1c213eee81b 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -624,6 +624,35 @@ public class ContextWrapper extends Context { mBase.sendStickyBroadcast(intent); } + /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Override + @Deprecated + public void sendStickyBroadcast(@NonNull Intent intent, @Nullable Bundle options) { + mBase.sendStickyBroadcast(intent, options); + } + @Override @Deprecated public void sendStickyOrderedBroadcast(Intent intent, diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 7843d97aa411..1752b480c06b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6681,6 +6681,25 @@ public class Intent implements Parcelable, Cloneable { | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION; + /** + * Local flag indicating this instance was created by copy constructor. + */ + private static final int LOCAL_FLAG_FROM_COPY = 1 << 0; + + /** + * Local flag indicating this instance was created from a {@link Parcel}. + */ + private static final int LOCAL_FLAG_FROM_PARCEL = 1 << 1; + + /** + * Local flag indicating this instance was delivered through a protected + * component, such as an activity that requires a signature permission, or a + * protected broadcast. Note that this flag <em>cannot</em> be recursively + * applied to any contained instances, since a malicious app may have + * controlled them via {@link #fillIn(Intent, int)}. + */ + private static final int LOCAL_FLAG_FROM_PROTECTED_COMPONENT = 1 << 2; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // toUri() and parseUri() options. @@ -6798,6 +6817,8 @@ public class Intent implements Parcelable, Cloneable { private String mPackage; private ComponentName mComponent; private int mFlags; + /** Set of in-process flags which are never parceled */ + private int mLocalFlags; private ArraySet<String> mCategories; @UnsupportedAppUsage private Bundle mExtras; @@ -6848,6 +6869,11 @@ public class Intent implements Parcelable, Cloneable { this.mCategories = new ArraySet<>(o.mCategories); } + // Inherit flags from the original, plus mark that we were + // created by this copy constructor + this.mLocalFlags = o.mLocalFlags; + this.mLocalFlags |= LOCAL_FLAG_FROM_COPY; + if (copyMode != COPY_MODE_FILTER) { this.mFlags = o.mFlags; this.mContentUserHint = o.mContentUserHint; @@ -10931,6 +10957,9 @@ public class Intent implements Parcelable, Cloneable { /** @hide */ protected Intent(Parcel in) { + // Remember that we came from a remote process to help detect security + // issues caused by later unsafe launches + mLocalFlags = LOCAL_FLAG_FROM_PARCEL; readFromParcel(in); } @@ -11242,18 +11271,27 @@ public class Intent implements Parcelable, Cloneable { mData = Uri.fromFile(after); } } + + // Detect cases where we're about to launch a potentially unsafe intent + if ((mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0 + && (mLocalFlags & LOCAL_FLAG_FROM_PROTECTED_COMPONENT) == 0 + && StrictMode.vmUnsafeIntentLaunchEnabled()) { + StrictMode.onUnsafeIntentLaunch(this); + } } /** * @hide */ - public void prepareToEnterProcess() { + public void prepareToEnterProcess(boolean fromProtectedComponent) { // We just entered destination process, so we should be able to read all // parcelables inside. setDefusable(true); if (mSelector != null) { - mSelector.prepareToEnterProcess(); + // We can't recursively claim that this data is from a protected + // component, since it may have been filled in by a malicious app + mSelector.prepareToEnterProcess(false); } if (mClipData != null) { mClipData.prepareToEnterProcess(); @@ -11265,6 +11303,10 @@ public class Intent implements Parcelable, Cloneable { mContentUserHint = UserHandle.USER_CURRENT; } } + + if (fromProtectedComponent) { + mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT; + } } /** @hide */ diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl index 44b5c4482599..0b950b461285 100644 --- a/core/java/android/content/om/IOverlayManager.aidl +++ b/core/java/android/content/om/IOverlayManager.aidl @@ -17,6 +17,7 @@ package android.content.om; import android.content.om.OverlayInfo; +import android.content.om.OverlayManagerTransaction; /** * Api for getting information about overlay packages. @@ -163,4 +164,18 @@ interface IOverlayManager { * @param packageName The name of the overlay package whose idmap should be deleted. */ void invalidateCachesForOverlay(in String packageName, in int userIs); + + /** + * Perform a series of requests related to overlay packages. This is an + * atomic operation: either all requests were performed successfully and + * the changes were propagated to the rest of the system, or at least one + * request could not be performed successfully and nothing is changed and + * nothing is propagated to the rest of the system. + * + * @see OverlayManagerTransaction + * + * @param transaction the series of overlay related requests to perform + * @throws SecurityException if the transaction failed + */ + void commit(in OverlayManagerTransaction transaction); } diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 217f637cf9e3..7c14c2891d01 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -254,6 +254,29 @@ public class OverlayManager { } /** + * Perform a series of requests related to overlay packages. This is an + * atomic operation: either all requests were performed successfully and + * the changes were propagated to the rest of the system, or at least one + * request could not be performed successfully and nothing is changed and + * nothing is propagated to the rest of the system. + * + * @see OverlayManagerTransaction + * + * @param transaction the series of overlay related requests to perform + * @throws Exception if not all the requests could be successfully and + * atomically executed + * + * @hide + */ + public void commit(@NonNull final OverlayManagerTransaction transaction) { + try { + mService.commit(transaction); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starting on R, actor enforcement and app visibility changes introduce additional failure * cases, but the SecurityException thrown with these checks is unexpected for existing * consumers of the API. diff --git a/core/java/android/content/om/OverlayManagerTransaction.aidl b/core/java/android/content/om/OverlayManagerTransaction.aidl new file mode 100644 index 000000000000..6715c82d4e6f --- /dev/null +++ b/core/java/android/content/om/OverlayManagerTransaction.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.om; + +parcelable OverlayManagerTransaction; diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java new file mode 100644 index 000000000000..1fa8973c35b5 --- /dev/null +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.om; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Container for a batch of requests to the OverlayManagerService. + * + * Transactions are created using a builder interface. Example usage: + * + * final OverlayManager om = ctx.getSystemService(OverlayManager.class); + * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder() + * .setEnabled(...) + * .setEnabled(...) + * .build(); + * om.commit(t); + * + * @hide + */ +public class OverlayManagerTransaction + implements Iterable<OverlayManagerTransaction.Request>, Parcelable { + // TODO: remove @hide from this class when OverlayManager is added to the + // SDK, but keep OverlayManagerTransaction.Request @hidden + private final List<Request> mRequests; + + OverlayManagerTransaction(@NonNull final List<Request> requests) { + checkNotNull(requests); + if (requests.contains(null)) { + throw new IllegalArgumentException("null request"); + } + mRequests = requests; + } + + private OverlayManagerTransaction(@NonNull final Parcel source) { + final int size = source.readInt(); + mRequests = new ArrayList<Request>(size); + for (int i = 0; i < size; i++) { + final int request = source.readInt(); + final String packageName = source.readString(); + final int userId = source.readInt(); + mRequests.add(new Request(request, packageName, userId)); + } + } + + @Override + public Iterator<Request> iterator() { + return mRequests.iterator(); + } + + @Override + public String toString() { + return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests); + } + + /** + * A single unit of the transaction, such as a request to enable an + * overlay, or to disable an overlay. + * + * @hide + */ + public static class Request { + @IntDef(prefix = "TYPE_", value = { + TYPE_SET_ENABLED, + TYPE_SET_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RequestType {} + + public static final int TYPE_SET_ENABLED = 0; + public static final int TYPE_SET_DISABLED = 1; + + @RequestType public final int type; + public final String packageName; + public final int userId; + + public Request(@RequestType final int type, @NonNull final String packageName, + final int userId) { + this.type = type; + this.packageName = packageName; + this.userId = userId; + } + + @Override + public String toString() { + return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}", + type, typeToString(), packageName, userId); + } + + /** + * Translate the request type into a human readable string. Only + * intended for debugging. + * + * @hide + */ + public String typeToString() { + switch (type) { + case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED"; + case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED"; + default: return String.format("TYPE_UNKNOWN (0x%02x)", type); + } + } + } + + /** + * Builder class for OverlayManagerTransaction objects. + * + * @hide + */ + public static class Builder { + private final List<Request> mRequests = new ArrayList<>(); + + /** + * Request that an overlay package be enabled and change its loading + * order to the last package to be loaded, or disabled + * + * If the caller has the correct permissions, it is always possible to + * disable an overlay. Due to technical and security reasons it may not + * always be possible to enable an overlay, for instance if the overlay + * does not successfully overlay any target resources due to + * overlayable policy restrictions. + * + * An enabled overlay is a part of target package's resources, i.e. it will + * be part of any lookups performed via {@link android.content.res.Resources} + * and {@link android.content.res.AssetManager}. A disabled overlay will no + * longer affect the resources of the target package. If the target is + * currently running, its outdated resources will be replaced by new ones. + * + * @param packageName The name of the overlay package. + * @param enable true to enable the overlay, false to disable it. + * @return this Builder object, so you can chain additional requests + */ + public Builder setEnabled(@NonNull String packageName, boolean enable) { + return setEnabled(packageName, enable, UserHandle.myUserId()); + } + + /** + * @hide + */ + public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) { + checkNotNull(packageName); + @Request.RequestType final int type = + enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED; + mRequests.add(new Request(type, packageName, userId)); + return this; + } + + /** + * Create a new transaction out of the requests added so far. Execute + * the transaction by calling OverlayManager#commit. + * + * @see OverlayManager#commit + * @return a new transaction + */ + public OverlayManagerTransaction build() { + return new OverlayManagerTransaction(mRequests); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + final int size = mRequests.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final Request req = mRequests.get(i); + dest.writeInt(req.type); + dest.writeString(req.packageName); + dest.writeInt(req.userId); + } + } + + public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR = + new Parcelable.Creator<OverlayManagerTransaction>() { + + @Override + public OverlayManagerTransaction createFromParcel(Parcel source) { + return new OverlayManagerTransaction(source); + } + + @Override + public OverlayManagerTransaction[] newArray(int size) { + return new OverlayManagerTransaction[size]; + } + }; +} diff --git a/core/java/android/content/pm/AppSearchPerson.java b/core/java/android/content/pm/AppSearchPerson.java index 045c55f28bf1..d70ac918e208 100644 --- a/core/java/android/content/pm/AppSearchPerson.java +++ b/core/java/android/content/pm/AppSearchPerson.java @@ -47,32 +47,24 @@ public class AppSearchPerson extends GenericDocument { } public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE) - .addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_NAME) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_NAME) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_KEY) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_KEY) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_IS_BOT) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN) + ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_BOT) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_IS_IMPORTANT) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN) + ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_IMPORTANT) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() ).build(); diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 14b8df86025c..85549d854c6d 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -73,131 +73,100 @@ public class AppSearchShortcutInfo extends GenericDocument { public static final String KEY_DISABLED_REASON = "disabledReason"; public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE) - .addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_PACKAGE_NAME) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_PACKAGE_NAME) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_ACTIVITY) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ACTIVITY) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_TITLE) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_TITLE) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_TEXT) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_TEXT) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_DISABLED_MESSAGE) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_MESSAGE) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_CATEGORIES) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CATEGORIES) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_INTENTS) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_INTENTS) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_INTENT_PERSISTABLE_EXTRAS) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES) + ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder( + KEY_INTENT_PERSISTABLE_EXTRAS) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_PERSON) + ).addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(KEY_PERSON) .setSchemaType(AppSearchPerson.SCHEMA_TYPE) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_LOCUS_ID) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LOCUS_ID) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_RANK) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64) + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_RANK) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_EXTRAS) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES) + ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_EXTRAS) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_FLAGS) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64) + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_FLAGS) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_ICON_RES_ID) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64) + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_ICON_RES_ID) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_ICON_RES_NAME) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ICON_RES_NAME) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_ICON_URI) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ICON_URI) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_BITMAP_PATH) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_BITMAP_PATH) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) .build() - ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_DISABLED_REASON) - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64) + ).addProperty(new AppSearchSchema.Int64PropertyConfig.Builder(KEY_DISABLED_REASON) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) - .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS) .build() ).build(); diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index a54e88f2d493..dbcd79dc5da2 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -41,4 +41,7 @@ interface ISensorPrivacyManager { void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable); // =============== End of transactions used on native side as well ============================ + + void suppressIndividualSensorPrivacyReminders(int userId, String packageName, IBinder token, + boolean suppress); }
\ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index e165ad694fcc..f4f9e1775d1a 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -22,6 +22,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -44,6 +45,18 @@ import java.lang.annotation.RetentionPolicy; @TestApi @SystemService(Context.SENSOR_PRIVACY_SERVICE) public final class SensorPrivacyManager { + /** + * Unique Id of this manager to identify to the service + * @hide + */ + private IBinder token = new Binder(); + + /** + * An extra containing a sensor + * @hide + */ + public static final String EXTRA_SENSOR = SensorPrivacyManager.class.getName() + + ".extra.sensor"; /** Microphone * @hide */ @@ -299,4 +312,22 @@ public final class SensorPrivacyManager { throw e.rethrowFromSystemServer(); } } + + /** + * Don't show dialogs to turn off sensor privacy for this package. + * + * @param packageName Package name not to show dialogs for + * @param suppress Whether to suppress or re-enable. + * + * @hide + */ + public void suppressIndividualSensorPrivacyReminders(@NonNull String packageName, + boolean suppress) { + try { + mService.suppressIndividualSensorPrivacyReminders(mContext.getUserId(), packageName, + token, suppress); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl index fcdf61e99471..c854ac9847d8 100644 --- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl +++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl @@ -38,7 +38,7 @@ interface IBiometricAuthenticator { SensorPropertiesInternal getSensorProperties(String opPackageName); // Requests a proto dump of the sensor. See biometrics.proto - byte[] dumpSensorServiceStateProto(); + byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer); // This method prepares the service to start authenticating, but doesn't start authentication. // This is protected by the MANAGE_BIOMETRIC signature permission. This method should only be diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 5a03adee4eab..421a07ba93e1 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -47,28 +47,40 @@ public abstract class DisplayManagerInternal { * begins adjusting the power state to match what was requested. * </p> * + * @param groupId The identifier for the display group being requested to change power state * @param request The requested power state. - * @param waitForNegativeProximity If true, issues a request to wait for + * @param waitForNegativeProximity If {@code true}, issues a request to wait for * negative proximity before turning the screen back on, assuming the screen * was turned off by the proximity sensor. - * @return True if display is ready, false if there are important changes that must - * be made asynchronously (such as turning the screen on), in which case the caller - * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()} - * then try the request again later until the state converges. + * @return {@code true} if display group is ready, {@code false} if there are important + * changes that must be made asynchronously (such as turning the screen on), in which case + * the caller should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged} + * then try the request again later until the state converges. If the provided {@code groupId} + * cannot be found then {@code true} will be returned. */ - public abstract boolean requestPowerState(DisplayPowerRequest request, + public abstract boolean requestPowerState(int groupId, DisplayPowerRequest request, boolean waitForNegativeProximity); /** - * Returns true if the proximity sensor screen-off function is available. + * Returns {@code true} if the proximity sensor screen-off function is available. */ public abstract boolean isProximitySensorAvailable(); /** - * Returns the id of the {@link com.android.server.display.DisplayGroup} to which the provided - * display belongs. + * Registers a display group listener which will be informed of the addition, removal, or change + * of display groups. + * + * @param listener The listener to register. */ - public abstract int getDisplayGroupId(int displayId); + public abstract void registerDisplayGroupListener(DisplayGroupListener listener); + + /** + * Unregisters a display group listener which will be informed of the addition, removal, or + * change of display groups. + * + * @param listener The listener to unregister. + */ + public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener); /** * Screenshot for internal system-only use such as rotation, etc. This method includes @@ -451,7 +463,7 @@ public abstract class DisplayManagerInternal { void onStateChanged(); void onProximityPositive(); void onProximityNegative(); - void onDisplayStateChange(int state); // one of the Display state constants + void onDisplayStateChange(boolean allInactive, boolean allOff); void acquireSuspendBlocker(); void releaseSuspendBlocker(); @@ -465,4 +477,33 @@ public abstract class DisplayManagerInternal { public interface DisplayTransactionListener { void onDisplayTransaction(Transaction t); } + + /** + * Called when there are changes to {@link com.android.server.display.DisplayGroup + * DisplayGroups}. + */ + public interface DisplayGroupListener { + /** + * A new display group with the provided {@code groupId} was added. + * This is guaranteed to be called <i>before</i> any corresponding calls to + * {@link android.hardware.display.DisplayManager.DisplayListener} are made. + */ + void onDisplayGroupAdded(int groupId); + + /** + * The display group with the provided {@code groupId} was removed. + * + * This is guaranteed to be called <i>after</i> any corresponding calls to + * {@link android.hardware.display.DisplayManager.DisplayListener} are made. + */ + void onDisplayGroupRemoved(int groupId); + + /** + * The display group with the provided {@code groupId} has changed. + * + * This is guaranteed to be called <i>after</i> any corresponding calls to + * {@link android.hardware.display.DisplayManager.DisplayListener} are made. + */ + void onDisplayGroupChanged(int groupId); + } } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 3b19f12a41ba..1b188e87e90f 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -35,7 +35,7 @@ interface IFaceService { ITestSession createTestSession(int sensorId, String opPackageName); // Requests a proto dump of the specified sensor - byte[] dumpSensorServiceStateProto(int sensorId); + byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer); // Retrieve static sensor properties for all face sensors List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 582570e63d54..d93286531465 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -89,6 +89,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing private static final int MSG_REMOVED = 105; private static final int MSG_CHALLENGE_GENERATED = 106; private static final int MSG_FINGERPRINT_DETECTED = 107; + private static final int MSG_UDFPS_POINTER_DOWN = 108; + private static final int MSG_UDFPS_POINTER_UP = 109; /** * Request authentication with any single sensor. @@ -338,6 +340,20 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @Override public void onAuthenticationAcquired(int acquireInfo) {} + + /** + * Invoked for under-display fingerprint sensors when a touch has been detected on the + * sensor area. + * @hide + */ + public void onUdfpsPointerDown(int sensorId) {} + + /** + * Invoked for under-display fingerprint sensors when a touch has been removed from the + * sensor area. + * @hide + */ + public void onUdfpsPointerUp(int sensorId) {} } /** @@ -1005,6 +1021,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */, (boolean) msg.obj /* isStrongBiometric */); break; + case MSG_UDFPS_POINTER_DOWN: + sendUdfpsPointerDown(msg.arg1 /* sensorId */); + break; + case MSG_UDFPS_POINTER_UP: + sendUdfpsPointerUp(msg.arg1 /* sensorId */); + break; default: Slog.w(TAG, "Unknown message: " + msg.what); @@ -1103,6 +1125,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric); } + private void sendUdfpsPointerDown(int sensorId) { + if (mAuthenticationCallback == null) { + Slog.e(TAG, "sendUdfpsPointerDown, callback null"); + return; + } + mAuthenticationCallback.onUdfpsPointerDown(sensorId); + } + + private void sendUdfpsPointerUp(int sensorId) { + if (mAuthenticationCallback == null) { + Slog.e(TAG, "sendUdfpsPointerUp, callback null"); + return; + } + mAuthenticationCallback.onUdfpsPointerUp(sensorId); + } + /** * @hide */ @@ -1280,6 +1318,17 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge) .sendToTarget(); } + + @Override // binder call + public void onUdfpsPointerDown(int sensorId) { + mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget(); + } + + @Override // binder call + public void onUdfpsPointerUp(int sensorId) { + mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget(); + + } }; } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 74c5b5864e87..3657a83039ad 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -36,7 +36,7 @@ interface IFingerprintService { ITestSession createTestSession(int sensorId, String opPackageName); // Requests a proto dump of the specified sensor - byte[] dumpSensorServiceStateProto(int sensorId); + byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer); // Retrieve static sensor properties for all fingerprint sensors List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName); diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index 095b8e9527ad..1bd284d1ec05 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -30,4 +30,6 @@ oneway interface IFingerprintServiceReceiver { void onError(int error, int vendorCode); void onRemoved(in Fingerprint fp, int remaining); void onChallengeGenerated(int sensorId, long challenge); + void onUdfpsPointerDown(int sensorId); + void onUdfpsPointerUp(int sensorId); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 899af5a08b4d..2e45ed8504c9 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -19,6 +19,7 @@ import static android.net.IpSecManager.INVALID_RESOURCE_ID; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.QosCallback.QosCallbackRegistrationException; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -29,7 +30,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -4823,6 +4823,8 @@ public class ConnectivityManager { /** * Simulates a Data Stall for the specified Network. * + * <p>This method should only be used for tests. + * * <p>The caller must be the owner of the specified Network. * * @param detectionMethod The detection method used to identify the Data Stall. @@ -4832,7 +4834,7 @@ public class ConnectivityManager { * @throws SecurityException if the caller is not the owner of the given network. * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int detectionMethod, long timestampMillis, @@ -4848,4 +4850,118 @@ public class ConnectivityManager { Log.d(TAG, "setOemNetworkPreference called with preference: " + preference.toString()); } + + @NonNull + private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>(); + + /** + * Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will + * receive available QoS events related to the {@link Network} and local ip + port + * specified within socketInfo. + * <p/> + * The same {@link QosCallback} must be unregistered before being registered a second time, + * otherwise {@link QosCallbackRegistrationException} is thrown. + * <p/> + * This API does not, in itself, require any permission if called with a network that is not + * restricted. However, the underlying implementation currently only supports the IMS network, + * which is always restricted. That means non-preinstalled callers can't possibly find this API + * useful, because they'd never be called back on networks that they would have access to. + * + * @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is + * missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * Exceptions after the time of registration is passed through + * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. + * + * @param socketInfo the socket information used to match QoS events + * @param callback receives qos events that satisfy socketInfo + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + * + * @hide + */ + @SystemApi + public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final QosCallback callback, + @CallbackExecutor @NonNull final Executor executor) { + Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(executor, "executor must be non-null"); + + try { + synchronized (mQosCallbackConnections) { + if (getQosCallbackConnection(callback) == null) { + final QosCallbackConnection connection = + new QosCallbackConnection(this, callback, executor); + mQosCallbackConnections.add(connection); + mService.registerQosSocketCallback(socketInfo, connection); + } else { + Log.e(TAG, "registerQosCallback: Callback already registered"); + throw new QosCallbackRegistrationException(); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + + // The same unregister method method is called for consistency even though nothing + // will be sent to the ConnectivityService since the callback was never successfully + // registered. + unregisterQosCallback(callback); + e.rethrowFromSystemServer(); + } catch (final ServiceSpecificException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + unregisterQosCallback(callback); + throw convertServiceException(e); + } + } + + /** + * Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive + * events once unregistered and can be registered a second time. + * <p/> + * If the {@link QosCallback} does not have an active registration, it is a no-op. + * + * @param callback the callback being unregistered + * + * @hide + */ + @SystemApi + public void unregisterQosCallback(@NonNull final QosCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + try { + synchronized (mQosCallbackConnections) { + final QosCallbackConnection connection = getQosCallbackConnection(callback); + if (connection != null) { + connection.stopReceivingMessages(); + mService.unregisterQosCallback(connection); + mQosCallbackConnections.remove(connection); + } else { + Log.d(TAG, "unregisterQosCallback: Callback not registered"); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Gets the connection related to the callback. + * + * @param callback the callback to look up + * @return the related connection + */ + @Nullable + private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) { + for (final QosCallbackConnection connection : mQosCallbackConnections) { + // Checking by reference here is intentional + if (connection.getCallback() == callback) { + return connection; + } + } + return null; + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 5e925b6a2bd8..6fecee632d62 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -20,6 +20,8 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IQosCallback; +import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgentConfig; @@ -27,9 +29,9 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; -import android.net.ISocketKeepaliveCallback; import android.net.ProxyInfo; import android.net.UidRange; +import android.net.QosSocketInfo; import android.os.Bundle; import android.os.IBinder; import android.os.INetworkActivityListener; @@ -206,11 +208,11 @@ interface IConnectivityManager void startNattKeepalive(in Network network, int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr); - void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId, + void startNattKeepaliveWithFd(in Network network, in ParcelFileDescriptor pfd, int resourceId, int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr, String dstAddr); - void startTcpKeepalive(in Network network, in FileDescriptor fd, int intervalSeconds, + void startTcpKeepalive(in Network network, in ParcelFileDescriptor pfd, int intervalSeconds, in ISocketKeepaliveCallback cb); void stopKeepalive(in Network network, int slot); @@ -239,4 +241,7 @@ interface IConnectivityManager void unregisterNetworkActivityListener(in INetworkActivityListener l); boolean isDefaultNetworkActive(); + + void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); + void unregisterQosCallback(in IQosCallback callback); } diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index fe9141cb6a20..dfb1e996c55a 100644 --- a/core/java/android/net/INetworkPolicyListener.aidl +++ b/core/java/android/net/INetworkPolicyListener.aidl @@ -23,6 +23,6 @@ oneway interface INetworkPolicyListener { void onMeteredIfacesChanged(in String[] meteredIfaces); void onRestrictBackgroundChanged(boolean restrictBackground); void onUidPoliciesChanged(int uid, int uidPolicies); - void onSubscriptionOverride(int subId, int overrideMask, int overrideValue); + void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes); void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 792e5b410afc..84a2acc165c4 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -76,9 +76,10 @@ interface INetworkPolicyManager { SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); String getSubscriptionPlansOwner(int subId); - void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage); + void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes, long timeoutMillis, String callingPackage); void factoryReset(String subscriber); boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork); + boolean isUidRestrictedOnMeteredNetworks(int uid); } diff --git a/core/java/android/net/IQosCallback.aidl b/core/java/android/net/IQosCallback.aidl new file mode 100644 index 000000000000..91c75759f85c --- /dev/null +++ b/core/java/android/net/IQosCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.Bundle; +import android.net.QosSession; +import android.telephony.data.EpsBearerQosSessionAttributes; + +/** + * AIDL interface for QosCallback + * + * @hide + */ +oneway interface IQosCallback +{ + void onQosEpsBearerSessionAvailable(in QosSession session, + in EpsBearerQosSessionAttributes attributes); + void onQosSessionLost(in QosSession session); + void onError(in int type); +} diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java index b0ce0c71fbeb..a15d165e65e7 100644 --- a/core/java/android/net/NattSocketKeepalive.java +++ b/core/java/android/net/NattSocketKeepalive.java @@ -51,7 +51,7 @@ public final class NattSocketKeepalive extends SocketKeepalive { void startImpl(int intervalSec) { mExecutor.execute(() -> { try { - mService.startNattKeepaliveWithFd(mNetwork, mPfd.getFileDescriptor(), mResourceId, + mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId, intervalSec, mCallback, mSource.getHostAddress(), mDestination.getHostAddress()); } catch (RemoteException e) { diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 4f46736c087d..d22d82d1f4d0 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; import com.android.connectivity.aidl.INetworkAgent; @@ -228,12 +229,6 @@ public abstract class NetworkAgent { public static final String REDIRECT_URL_KEY = "redirect URL"; /** - * Bundle key for the underlying networks in {@code EVENT_UNDERLYING_NETWORKS_CHANGED}. - * @hide - */ - public static final String UNDERLYING_NETWORKS_KEY = "underlyingNetworks"; - - /** * Sent by the NetworkAgent to ConnectivityService to indicate this network was * explicitly selected. This should be sent before the NetworkInfo is marked * CONNECTED so it can be given special treatment at that time. @@ -347,6 +342,24 @@ public abstract class NetworkAgent { */ private static final int EVENT_AGENT_DISCONNECTED = BASE + 19; + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with + * callback. + * + * arg1 = QoS agent callback ID + * obj = {@link QosFilter} + * @hide + */ + public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback. + * + * arg1 = QoS agent callback ID + * @hide + */ + public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. @@ -526,6 +539,17 @@ public abstract class NetworkAgent { onRemoveKeepalivePacketFilter(msg.arg1 /* slot */); break; } + case CMD_REGISTER_QOS_CALLBACK: { + onQosCallbackRegistered( + msg.arg1 /* QoS callback id */, + (QosFilter) msg.obj /* QoS filter */); + break; + } + case CMD_UNREGISTER_QOS_CALLBACK: { + onQosCallbackUnregistered( + msg.arg1 /* QoS callback id */); + break; + } } } } @@ -559,6 +583,8 @@ public abstract class NetworkAgent { } private static class NetworkAgentBinder extends INetworkAgent.Stub { + private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName(); + private final Handler mHandler; private NetworkAgentBinder(Handler handler) { @@ -645,6 +671,25 @@ public abstract class NetworkAgent { mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0)); } + + @Override + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilterParcelable qosFilterParcelable) { + if (qosFilterParcelable.getQosFilter() != null) { + mHandler.sendMessage( + mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0, + qosFilterParcelable.getQosFilter())); + return; + } + + Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null."); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); + } } /** @@ -1073,8 +1118,68 @@ public abstract class NetworkAgent { protected void preventAutomaticReconnect() { } + /** + * Called when a qos callback is registered with a filter. + * @param qosCallbackId the id for the callback registered + * @param filter the filter being registered + */ + public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) { + } + + /** + * Called when a qos callback is registered with a filter. + * <p/> + * Any QoS events that are sent with the same callback id after this method is called + * are a no-op. + * + * @param qosCallbackId the id for the callback being unregistered + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + } + + + /** + * Sends the attributes of Eps Bearer Qos Session back to the Application + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param attributes the attributes of the Eps Qos Session + */ + public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, + @NonNull final EpsBearerQosSessionAttributes attributes) { + Objects.requireNonNull(attributes, "The attributes must be non-null"); + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + attributes)); + } + + /** + * Sends event that the Eps Qos Session was lost. + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + */ + public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + } + + /** + * Sends the exception type back to the application. + * + * The NetworkAgent should not send anymore messages with this id. + * + * @param qosCallbackId the callback id this exception belongs to + * @param exceptionType the type of exception + */ + public final void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType)); + } + + /** @hide */ - protected void log(String s) { + protected void log(final String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); } } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 2d9f6d806f31..0a895b98f9fd 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; import android.os.Build; @@ -576,7 +575,6 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @UnsupportedAppUsage - @TestApi public @NetCapability int[] getCapabilities() { return BitUtils.unpackBits(mNetworkCapabilities); } @@ -821,7 +819,7 @@ public final class NetworkCapabilities implements Parcelable { * * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int TRANSPORT_TEST = 7; /** @hide */ diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index cad0db257ef6..ed169e75bd37 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -34,6 +34,7 @@ import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Process; import android.os.RemoteException; +import android.telephony.Annotation; import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -377,6 +378,8 @@ public class NetworkPolicyManager { * @param overrideMask the bitmask that specifies which of the overrides is being * set or cleared. * @param overrideValue the override values to set or clear. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} * @param timeoutMillis the timeout after which the requested override will * be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, @@ -385,11 +388,12 @@ public class NetworkPolicyManager { * @hide */ public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue, long timeoutMillis, - @NonNull String callingPackage) { + @SubscriptionOverrideMask int overrideValue, + @NonNull @Annotation.NetworkType int[] networkTypes, long timeoutMillis, + @NonNull String callingPackage) { try { - mService.setSubscriptionOverride(subId, overrideMask, overrideValue, timeoutMillis, - callingPackage); + mService.setSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes, + timeoutMillis, callingPackage); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -460,6 +464,22 @@ public class NetworkPolicyManager { } /** + * Check that the given uid is restricted from doing networking on metered networks. + * + * @param uid The target uid. + * @return true if the given uid is restricted from doing networking on metered networks. + * + * @hide + */ + public boolean isUidRestrictedOnMeteredNetworks(int uid) { + try { + return mService.isUidRestrictedOnMeteredNetworks(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Get multipath preference for the given network. */ public int getMultipathPreference(Network network) { @@ -597,9 +617,10 @@ public class NetworkPolicyManager { * @param subId the subscriber this override applies to. * @param overrideMask a bitmask that specifies which of the overrides is set. * @param overrideValue a bitmask that specifies the override values. + * @param networkTypes the network types this override applies to. */ public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue) {} + @SubscriptionOverrideMask int overrideValue, int[] networkTypes) {} /** * Notify of subscription plans change about a given subscription. @@ -623,8 +644,8 @@ public class NetworkPolicyManager { @Override public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue) { - mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue); + @SubscriptionOverrideMask int overrideValue, int[] networkTypes) { + mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); } @Override @@ -640,7 +661,7 @@ public class NetworkPolicyManager { @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { } @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { } @Override public void onSubscriptionOverride(int subId, int overrideMask, - int overrideValue) { } + int overrideValue, int[] networkTypes) { } @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { } } } diff --git a/core/java/android/net/NetworkReleasedException.java b/core/java/android/net/NetworkReleasedException.java new file mode 100644 index 000000000000..0629b7563aea --- /dev/null +++ b/core/java/android/net/NetworkReleasedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Indicates that the {@link Network} was released and is no longer available. + * + * @hide + */ +@SystemApi +public class NetworkReleasedException extends Exception { + /** @hide */ + public NetworkReleasedException() { + super("The network was released and is no longer available"); + } +} diff --git a/core/java/android/net/QosCallback.java b/core/java/android/net/QosCallback.java new file mode 100644 index 000000000000..22f06bc0e690 --- /dev/null +++ b/core/java/android/net/QosCallback.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.util.concurrent.Executor; + +/** + * Receives Qos information given a {@link Network}. The callback is registered with + * {@link ConnectivityManager#registerQosCallback}. + * + * <p> + * <br/> + * The callback will no longer receive calls if any of the following takes place: + * <ol> + * <li>{@link ConnectivityManager#unregisterQosCallback(QosCallback)} is called with the same + * callback instance.</li> + * <li>{@link QosCallback#onError(QosCallbackException)} is called.</li> + * <li>A network specific issue occurs. eg. Congestion on a carrier network.</li> + * <li>The network registered with the callback has no associated QoS providers</li> + * </ul> + * {@hide} + */ +@SystemApi +public abstract class QosCallback { + /** + * Invoked after an error occurs on a registered callback. Once called, the callback is + * automatically unregistered and the callback will no longer receive calls. + * + * <p>The underlying exception can either be a runtime exception or a custom exception made for + * {@link QosCallback}. see: {@link QosCallbackException}. + * + * @param exception wraps the underlying cause + */ + public void onError(@NonNull final QosCallbackException exception) { + } + + /** + * Called when a Qos Session first becomes available to the callback or if its attributes have + * changed. + * <p> + * Note: The callback may be called multiple times with the same attributes. + * + * @param session the available session + * @param sessionAttributes the attributes of the session + */ + public void onQosSessionAvailable(@NonNull final QosSession session, + @NonNull final QosSessionAttributes sessionAttributes) { + } + + /** + * Called after a Qos Session is lost. + * <p> + * At least one call to + * {@link QosCallback#onQosSessionAvailable(QosSession, QosSessionAttributes)} + * with the same {@link QosSession} will precede a call to lost. + * + * @param session the lost session + */ + public void onQosSessionLost(@NonNull final QosSession session) { + } + + /** + * Thrown when there is a problem registering {@link QosCallback} with + * {@link ConnectivityManager#registerQosCallback(QosSocketInfo, QosCallback, Executor)}. + */ + public static class QosCallbackRegistrationException extends RuntimeException { + /** + * @hide + */ + public QosCallbackRegistrationException() { + super(); + } + } +} diff --git a/core/java/android/net/QosCallbackConnection.java b/core/java/android/net/QosCallbackConnection.java new file mode 100644 index 000000000000..bdb4ad68cd7b --- /dev/null +++ b/core/java/android/net/QosCallbackConnection.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.data.EpsBearerQosSessionAttributes; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Sends messages from {@link com.android.server.ConnectivityService} to the registered + * {@link QosCallback}. + * <p/> + * This is a satellite class of {@link ConnectivityManager} and not meant + * to be used in other contexts. + * + * @hide + */ +class QosCallbackConnection extends android.net.IQosCallback.Stub { + + @NonNull private final ConnectivityManager mConnectivityManager; + @Nullable private volatile QosCallback mCallback; + @NonNull private final Executor mExecutor; + + @VisibleForTesting + @Nullable + public QosCallback getCallback() { + return mCallback; + } + + /** + * The constructor for the connection + * + * @param connectivityManager the mgr that created this connection + * @param callback the callback to send messages back to + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + */ + QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager, + @NonNull final QosCallback callback, + @NonNull final Executor executor) { + mConnectivityManager = Objects.requireNonNull(connectivityManager, + "connectivityManager must be non-null"); + mCallback = Objects.requireNonNull(callback, "callback must be non-null"); + mExecutor = Objects.requireNonNull(executor, "executor must be non-null"); + } + + /** + * Called when either the {@link EpsBearerQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onQosEpsBearerSessionAvailable(@NonNull final QosSession session, + @NonNull final EpsBearerQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + + /** + * Called when the session is lost. + * + * @param session the session that was lost + */ + @Override + public void onQosSessionLost(@NonNull final QosSession session) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionLost(session); + } + }); + } + + /** + * Called when there is an error on the registered callback. + * + * @param errorType the type of error + */ + @Override + public void onError(@QosCallbackException.ExceptionType final int errorType) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + // Messages no longer need to be received since there was an error. + stopReceivingMessages(); + mConnectivityManager.unregisterQosCallback(callback); + callback.onError(QosCallbackException.createException(errorType)); + } + }); + } + + /** + * The callback will stop receiving messages. + * <p/> + * There are no synchronization guarantees on exactly when the callback will stop receiving + * messages. + */ + void stopReceivingMessages() { + mCallback = null; + } +} diff --git a/core/java/android/net/QosCallbackException.java b/core/java/android/net/QosCallbackException.java new file mode 100644 index 000000000000..7fd9a527e2ac --- /dev/null +++ b/core/java/android/net/QosCallbackException.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This is the exception type passed back through the onError method on {@link QosCallback}. + * {@link QosCallbackException#getCause()} contains the actual error that caused this exception. + * + * The possible exception types as causes are: + * 1. {@link NetworkReleasedException} + * 2. {@link SocketNotBoundException} + * 3. {@link UnsupportedOperationException} + * 4. {@link SocketLocalAddressChangedException} + * + * @hide + */ +@SystemApi +public final class QosCallbackException extends Exception { + + /** @hide */ + @IntDef(prefix = {"EX_TYPE_"}, value = { + EX_TYPE_FILTER_NONE, + EX_TYPE_FILTER_NETWORK_RELEASED, + EX_TYPE_FILTER_SOCKET_NOT_BOUND, + EX_TYPE_FILTER_NOT_SUPPORTED, + EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ExceptionType {} + + private static final String TAG = "QosCallbackException"; + + // Types of exceptions supported // + /** {@hide} */ + public static final int EX_TYPE_FILTER_NONE = 0; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NETWORK_RELEASED = 1; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4; + + /** + * Creates exception based off of a type and message. Not all types of exceptions accept a + * custom message. + * + * {@hide} + */ + @NonNull + static QosCallbackException createException(@ExceptionType final int type) { + switch (type) { + case EX_TYPE_FILTER_NETWORK_RELEASED: + return new QosCallbackException(new NetworkReleasedException()); + case EX_TYPE_FILTER_SOCKET_NOT_BOUND: + return new QosCallbackException(new SocketNotBoundException()); + case EX_TYPE_FILTER_NOT_SUPPORTED: + return new QosCallbackException(new UnsupportedOperationException( + "This device does not support the specified filter")); + case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED: + return new QosCallbackException( + new SocketLocalAddressChangedException()); + default: + Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'"); + return new QosCallbackException( + new RuntimeException("Unknown exception code: " + type)); + } + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final String message) { + super(message); + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final Throwable cause) { + super(cause); + } +} diff --git a/core/java/android/net/QosFilter.java b/core/java/android/net/QosFilter.java new file mode 100644 index 000000000000..070546878171 --- /dev/null +++ b/core/java/android/net/QosFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * Provides the related filtering logic to the {@link NetworkAgent} to match {@link QosSession}s + * to their related {@link QosCallback}. + * + * Used by the {@link com.android.server.ConnectivityService} to validate a {@link QosCallback} + * is still able to receive a {@link QosSession}. + * + * @hide + */ +@SystemApi +public abstract class QosFilter { + + /** + * The constructor is kept hidden from outside this package to ensure that all derived types + * are known and properly handled when being passed to and from {@link NetworkAgent}. + * + * @hide + */ + QosFilter() { + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + public abstract Network getNetwork(); + + /** + * Validates that conditions have not changed such that no further {@link QosSession}s should + * be passed back to the {@link QosCallback} associated to this filter. + * + * @return the error code when present, otherwise the filter is valid + * + * @hide + */ + @QosCallbackException.ExceptionType + public abstract int validate(); +} + diff --git a/core/java/android/net/QosFilterParcelable.aidl b/core/java/android/net/QosFilterParcelable.aidl new file mode 100644 index 000000000000..312d6352ee92 --- /dev/null +++ b/core/java/android/net/QosFilterParcelable.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2020 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +parcelable QosFilterParcelable; + diff --git a/core/java/android/net/QosFilterParcelable.java b/core/java/android/net/QosFilterParcelable.java new file mode 100644 index 000000000000..da3b2cf8ff7a --- /dev/null +++ b/core/java/android/net/QosFilterParcelable.java @@ -0,0 +1,113 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; + +/** + * Aware of how to parcel different types of {@link QosFilter}s. Any new type of qos filter must + * have a specialized case written here. + * <p/> + * Specifically leveraged when transferring {@link QosFilter} from + * {@link com.android.server.ConnectivityService} to {@link NetworkAgent} when the filter is first + * registered. + * <p/> + * This is not meant to be used in other contexts. + * + * @hide + */ +public final class QosFilterParcelable implements Parcelable { + + private static final String LOG_TAG = QosFilterParcelable.class.getSimpleName(); + + // Indicates that the filter was not successfully written to the parcel. + private static final int NO_FILTER_PRESENT = 0; + + // The parcel is of type qos socket filter. + private static final int QOS_SOCKET_FILTER = 1; + + private final QosFilter mQosFilter; + + /** + * The underlying qos filter. + * <p/> + * Null only in the case parceling failed. + */ + @Nullable + public QosFilter getQosFilter() { + return mQosFilter; + } + + public QosFilterParcelable(@NonNull final QosFilter qosFilter) { + Objects.requireNonNull(qosFilter, "qosFilter must be non-null"); + + // NOTE: Normally a type check would belong here, but doing so breaks unit tests that rely + // on mocking qos filter. + mQosFilter = qosFilter; + } + + private QosFilterParcelable(final Parcel in) { + final int filterParcelType = in.readInt(); + + switch (filterParcelType) { + case QOS_SOCKET_FILTER: { + mQosFilter = new QosSocketFilter(QosSocketInfo.CREATOR.createFromParcel(in)); + break; + } + + case NO_FILTER_PRESENT: + default: { + mQosFilter = null; + } + } + } + + public static final Creator<QosFilterParcelable> CREATOR = new Creator<QosFilterParcelable>() { + @Override + public QosFilterParcelable createFromParcel(final Parcel in) { + return new QosFilterParcelable(in); + } + + @Override + public QosFilterParcelable[] newArray(final int size) { + return new QosFilterParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + if (mQosFilter instanceof QosSocketFilter) { + dest.writeInt(QOS_SOCKET_FILTER); + final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter; + qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0); + return; + } + dest.writeInt(NO_FILTER_PRESENT); + Log.e(LOG_TAG, "Parceling failed, unknown type of filter present: " + mQosFilter); + } +} diff --git a/core/java/android/net/QosSession.aidl b/core/java/android/net/QosSession.aidl new file mode 100644 index 000000000000..c2cf36624b55 --- /dev/null +++ b/core/java/android/net/QosSession.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2020 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +parcelable QosSession; + diff --git a/core/java/android/net/QosSession.java b/core/java/android/net/QosSession.java new file mode 100644 index 000000000000..4f3bb77c5877 --- /dev/null +++ b/core/java/android/net/QosSession.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Provides identifying information of a QoS session. Sent to an application through + * {@link QosCallback}. + * + * @hide + */ +@SystemApi +public final class QosSession implements Parcelable { + + /** + * The {@link QosSession} is a LTE EPS Session. + */ + public static final int TYPE_EPS_BEARER = 1; + + private final int mSessionId; + + private final int mSessionType; + + /** + * Gets the unique id of the session that is used to differentiate sessions across different + * types. + * <p/> + * Note: Different qos sessions can be provided by different actors. + * + * @return the unique id + */ + public long getUniqueId() { + return (long) mSessionType << 32 | mSessionId; + } + + /** + * Gets the session id that is unique within that type. + * <p/> + * Note: The session id is set by the actor providing the qos. It can be either manufactured by + * the actor, but also may have a particular meaning within that type. For example, using the + * bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes} + * is a straight forward way to keep the sessions unique from one another within that type. + * + * @return the id of the session + */ + public int getSessionId() { + return mSessionId; + } + + /** + * Gets the type of session. + */ + @QosSessionType + public int getSessionType() { + return mSessionType; + } + + /** + * Creates a {@link QosSession}. + * + * @param sessionId uniquely identifies the session across all sessions of the same type + * @param sessionType the type of session + */ + public QosSession(final int sessionId, @QosSessionType final int sessionType) { + //Ensures the session id is unique across types of sessions + mSessionId = sessionId; + mSessionType = sessionType; + } + + + @Override + public String toString() { + return "QosSession{" + + "mSessionId=" + mSessionId + + ", mSessionType=" + mSessionType + + '}'; + } + + /** + * Annotations for types of qos sessions. + */ + @IntDef(value = { + TYPE_EPS_BEARER, + }) + @interface QosSessionType {} + + private QosSession(final Parcel in) { + mSessionId = in.readInt(); + mSessionType = in.readInt(); + } + + @NonNull + public static final Creator<QosSession> CREATOR = new Creator<QosSession>() { + @NonNull + @Override + public QosSession createFromParcel(@NonNull final Parcel in) { + return new QosSession(in); + } + + @NonNull + @Override + public QosSession[] newArray(final int size) { + return new QosSession[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mSessionId); + dest.writeInt(mSessionType); + } +} diff --git a/core/java/android/net/QosSessionAttributes.java b/core/java/android/net/QosSessionAttributes.java new file mode 100644 index 000000000000..7a885942d1b5 --- /dev/null +++ b/core/java/android/net/QosSessionAttributes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Implemented by classes that encapsulate Qos related attributes that describe a Qos Session. + * + * Use the instanceof keyword to determine the underlying type. + * + * @hide + */ +@SystemApi +public interface QosSessionAttributes { +} diff --git a/core/java/android/net/QosSocketFilter.java b/core/java/android/net/QosSocketFilter.java new file mode 100644 index 000000000000..f51a0881e6e7 --- /dev/null +++ b/core/java/android/net/QosSocketFilter.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE; +import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import java.io.FileDescriptor; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Objects; + +/** + * Filters a {@link QosSession} according to the binding on the provided {@link Socket}. + * + * @hide + */ +public class QosSocketFilter extends QosFilter { + + private static final String TAG = QosSocketFilter.class.getSimpleName(); + + @NonNull + private final QosSocketInfo mQosSocketInfo; + + /** + * Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}. + * + * @param qosSocketInfo the information required to filter and validate + */ + public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) { + Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null"); + mQosSocketInfo = qosSocketInfo; + } + + /** + * Gets the parcelable qos socket info that was used to create the filter. + */ + @NonNull + public QosSocketInfo getQosSocketInfo() { + return mQosSocketInfo; + } + + /** + * Performs two validations: + * 1. If the socket is not bound, then return + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected + * by checking the local address on the filter which becomes null when the socket is no + * longer bound. + * 2. In the scenario that the socket is now bound to a different local address, which can + * happen in the case of UDP, then + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned. + * @return validation error code + */ + @Override + public int validate() { + final InetSocketAddress sa = getAddressFromFileDescriptor(); + if (sa == null) { + return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND; + } + + if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) { + return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + } + + return EX_TYPE_FILTER_NONE; + } + + /** + * The local address of the socket's binding. + * + * Note: If the socket is no longer bound, null is returned. + * + * @return the local address + */ + @Nullable + private InetSocketAddress getAddressFromFileDescriptor() { + final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor(); + if (parcelFileDescriptor == null) return null; + + final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor(); + if (fd == null) return null; + + final SocketAddress address; + try { + address = Os.getsockname(fd); + } catch (final ErrnoException e) { + Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e); + return null; + } + if (address instanceof InetSocketAddress) { + return (InetSocketAddress) address; + } + return null; + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + @Override + public Network getNetwork() { + return mQosSocketInfo.getNetwork(); + } +} diff --git a/core/java/android/net/QosSocketInfo.aidl b/core/java/android/net/QosSocketInfo.aidl new file mode 100644 index 000000000000..476c0900e23e --- /dev/null +++ b/core/java/android/net/QosSocketInfo.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2020 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +parcelable QosSocketInfo; + diff --git a/core/java/android/net/QosSocketInfo.java b/core/java/android/net/QosSocketInfo.java new file mode 100644 index 000000000000..d37c4691ddde --- /dev/null +++ b/core/java/android/net/QosSocketInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Used in conjunction with + * {@link ConnectivityManager#registerQosCallback} + * in order to receive Qos Sessions related to the local address and port of a bound {@link Socket}. + * + * @hide + */ +@SystemApi +public final class QosSocketInfo implements Parcelable { + + @NonNull + private final Network mNetwork; + + @NonNull + private final ParcelFileDescriptor mParcelFileDescriptor; + + @NonNull + private final InetSocketAddress mLocalSocketAddress; + + /** + * The {@link Network} the socket is on. + * + * @return the registered {@link Network} + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * The parcel file descriptor wrapped around the socket's file descriptor. + * + * @return the parcel file descriptor of the socket + */ + @NonNull + ParcelFileDescriptor getParcelFileDescriptor() { + return mParcelFileDescriptor; + } + + /** + * The local address of the socket passed into {@link QosSocketInfo(Network, Socket)}. + * The value does not reflect any changes that occur to the socket after it is first set + * in the constructor. + * + * @return the local address of the socket + */ + @NonNull + public InetSocketAddress getLocalSocketAddress() { + return mLocalSocketAddress; + } + + /** + * Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The + * {@link Socket} must remain bound in order to receive {@link QosSession}s. + * + * @param network the network + * @param socket the bound {@link Socket} + */ + public QosSocketInfo(@NonNull final Network network, @NonNull final Socket socket) + throws IOException { + Objects.requireNonNull(socket, "socket cannot be null"); + + mNetwork = Objects.requireNonNull(network, "network cannot be null"); + mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$()); + mLocalSocketAddress = + new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); + } + + /* Parcelable methods */ + private QosSocketInfo(final Parcel in) { + mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in)); + mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in); + + final int addressLength = in.readInt(); + mLocalSocketAddress = readSocketAddress(in, addressLength); + } + + private InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) { + final byte[] address = new byte[addressLength]; + in.readByteArray(address); + final int port = in.readInt(); + + try { + return new InetSocketAddress(InetAddress.getByAddress(address), port); + } catch (final UnknownHostException e) { + /* The catch block was purposely left empty. UnknownHostException will never be thrown + since the address provided is numeric and non-null. */ + } + return new InetSocketAddress(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + mNetwork.writeToParcel(dest, 0); + mParcelFileDescriptor.writeToParcel(dest, 0); + + final byte[] address = mLocalSocketAddress.getAddress().getAddress(); + dest.writeInt(address.length); + dest.writeByteArray(address); + dest.writeInt(mLocalSocketAddress.getPort()); + } + + @NonNull + public static final Parcelable.Creator<QosSocketInfo> CREATOR = + new Parcelable.Creator<QosSocketInfo>() { + @NonNull + @Override + public QosSocketInfo createFromParcel(final Parcel in) { + return new QosSocketInfo(in); + } + + @NonNull + @Override + public QosSocketInfo[] newArray(final int size) { + return new QosSocketInfo[size]; + } + }; +} diff --git a/core/java/android/net/SocketLocalAddressChangedException.java b/core/java/android/net/SocketLocalAddressChangedException.java new file mode 100644 index 000000000000..9daad83fd13e --- /dev/null +++ b/core/java/android/net/SocketLocalAddressChangedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when the local address of the socket has changed. + * + * @hide + */ +@SystemApi +public class SocketLocalAddressChangedException extends Exception { + /** @hide */ + public SocketLocalAddressChangedException() { + super("The local address of the socket changed"); + } +} diff --git a/core/java/android/net/SocketNotBoundException.java b/core/java/android/net/SocketNotBoundException.java new file mode 100644 index 000000000000..b1d7026ac981 --- /dev/null +++ b/core/java/android/net/SocketNotBoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when a previously bound socket becomes unbound. + * + * @hide + */ +@SystemApi +public class SocketNotBoundException extends Exception { + /** @hide */ + public SocketNotBoundException() { + super("The socket is unbound"); + } +} diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java index 436397ea7754..d89814d49bd0 100644 --- a/core/java/android/net/TcpSocketKeepalive.java +++ b/core/java/android/net/TcpSocketKeepalive.java @@ -21,7 +21,6 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; -import java.io.FileDescriptor; import java.util.concurrent.Executor; /** @hide */ @@ -54,8 +53,7 @@ final class TcpSocketKeepalive extends SocketKeepalive { void startImpl(int intervalSec) { mExecutor.execute(() -> { try { - final FileDescriptor fd = mPfd.getFileDescriptor(); - mService.startTcpKeepalive(mNetwork, fd, intervalSec, mCallback); + mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback); } catch (RemoteException e) { Log.e(TAG, "Error starting packet keepalive: ", e); throw e.rethrowFromSystemServer(); diff --git a/core/java/android/net/TestNetworkInterface.java b/core/java/android/net/TestNetworkInterface.java index 84550834be07..4449ff80180b 100644 --- a/core/java/android/net/TestNetworkInterface.java +++ b/core/java/android/net/TestNetworkInterface.java @@ -15,7 +15,8 @@ */ package android.net; -import android.annotation.TestApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -25,9 +26,11 @@ import android.os.Parcelable; * * @hide */ -@TestApi +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TestNetworkInterface implements Parcelable { + @NonNull private final ParcelFileDescriptor mFileDescriptor; + @NonNull private final String mInterfaceName; @Override @@ -36,29 +39,32 @@ public final class TestNetworkInterface implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE); out.writeString(mInterfaceName); } - public TestNetworkInterface(ParcelFileDescriptor pfd, String intf) { + public TestNetworkInterface(@NonNull ParcelFileDescriptor pfd, @NonNull String intf) { mFileDescriptor = pfd; mInterfaceName = intf; } - private TestNetworkInterface(Parcel in) { + private TestNetworkInterface(@NonNull Parcel in) { mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); mInterfaceName = in.readString(); } + @NonNull public ParcelFileDescriptor getFileDescriptor() { return mFileDescriptor; } + @NonNull public String getInterfaceName() { return mInterfaceName; } + @NonNull public static final Parcelable.Creator<TestNetworkInterface> CREATOR = new Parcelable.Creator<TestNetworkInterface>() { public TestNetworkInterface createFromParcel(Parcel in) { diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java index a0a563b37025..4e894143bf91 100644 --- a/core/java/android/net/TestNetworkManager.java +++ b/core/java/android/net/TestNetworkManager.java @@ -17,18 +17,21 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; +import android.annotation.SystemApi; import android.os.IBinder; import android.os.RemoteException; import com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.Collection; + /** * Class that allows creation and management of per-app, test-only networks * * @hide */ -@TestApi +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public class TestNetworkManager { /** * Prefix for tun interfaces created by this class. @@ -57,7 +60,7 @@ public class TestNetworkManager { * @param network The test network that should be torn down * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void teardownTestNetwork(@NonNull Network network) { try { mService.teardownTestNetwork(network.netId); @@ -102,7 +105,7 @@ public class TestNetworkManager { * @param binder A binder object guarding the lifecycle of this test network. * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { setupTestNetwork(iface, null, true, new int[0], binder); } @@ -127,12 +130,29 @@ public class TestNetworkManager { * @param linkAddrs an array of LinkAddresses to assign to the TUN interface * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the * TUN interface. + * @deprecated Use {@link #createTunInterface(Collection)} instead. * @hide */ - @TestApi + @Deprecated + @NonNull public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + return createTunInterface(Arrays.asList(linkAddrs)); + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) { try { - return mService.createTunInterface(linkAddrs); + final LinkAddress[] arr = new LinkAddress[linkAddrs.size()]; + return mService.createTunInterface(linkAddrs.toArray(arr)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -145,7 +165,8 @@ public class TestNetworkManager { * TAP interface. * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull public TestNetworkInterface createTapInterface() { try { return mService.createTapInterface(); diff --git a/core/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java index 8dfd4e182ec4..85e3fa3048ed 100644 --- a/core/java/android/net/util/MultinetworkPolicyTracker.java +++ b/core/java/android/net/util/MultinetworkPolicyTracker.java @@ -29,7 +29,6 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; -import android.os.UserHandle; import android.provider.Settings; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; @@ -114,8 +113,8 @@ public class MultinetworkPolicyTracker { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mContext.registerReceiverAsUser( - mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler); + mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter, + null /* broadcastPermission */, mHandler); reevaluate(); } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index ba29a15a91bb..fdc3d650d797 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -43,6 +43,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_AUDIO, POWER_COMPONENT_VIDEO, POWER_COMPONENT_FLASHLIGHT, + POWER_COMPONENT_MOBILE_RADIO, POWER_COMPONENT_SYSTEM_SERVICES, }) @Retention(RetentionPolicy.SOURCE) @@ -57,8 +58,9 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_VIDEO = 5; public static final int POWER_COMPONENT_FLASHLIGHT = 6; public static final int POWER_COMPONENT_SYSTEM_SERVICES = 7; + public static final int POWER_COMPONENT_MOBILE_RADIO = 8; - public static final int POWER_COMPONENT_COUNT = 8; + public static final int POWER_COMPONENT_COUNT = 9; public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000; public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999; @@ -87,6 +89,7 @@ public abstract class BatteryConsumer { TIME_COMPONENT_BLUETOOTH, TIME_COMPONENT_CAMERA, TIME_COMPONENT_FLASHLIGHT, + TIME_COMPONENT_MOBILE_RADIO, }) @Retention(RetentionPolicy.SOURCE) public static @interface TimeComponent { @@ -100,8 +103,9 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_AUDIO = 5; public static final int TIME_COMPONENT_VIDEO = 6; public static final int TIME_COMPONENT_FLASHLIGHT = 7; + public static final int TIME_COMPONENT_MOBILE_RADIO = 8; - public static final int TIME_COMPONENT_COUNT = 8; + public static final int TIME_COMPONENT_COUNT = 9; public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000; public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999; diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 8148c457ec99..e50494615f0c 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -318,6 +318,7 @@ public class Build { * @see #SDK_INT * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @TestApi public static final int FIRST_SDK_INT = SystemProperties .getInt("ro.product.first_api_level", 0); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 6fe57774f6f3..b39c182fcab6 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -86,7 +86,7 @@ interface IUserManager { Bundle getApplicationRestrictionsForUser(in String packageName, int userId); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); - int removeUserOrSetEphemeral(int userId); + int removeUserOrSetEphemeral(int userId, boolean evenWhenDisallowed); boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 814a248cbb04..cbb3ba90fb03 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -416,9 +416,21 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_QUIESCENT = 10; /** + * Go to sleep reason code: The last powered on display group has been removed. * @hide */ - public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_QUIESCENT; + public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED = 11; + + /** + * Go to sleep reason code: Every display group has been turned off. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF = 12; + + /** + * @hide + */ + public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF; /** * @hide @@ -435,6 +447,8 @@ public final class PowerManager { case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility"; case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend"; case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive"; + case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed"; + case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off"; default: return Integer.toString(sleepReason); } } @@ -521,11 +535,32 @@ public final class PowerManager { WAKE_REASON_WAKE_KEY, WAKE_REASON_WAKE_MOTION, WAKE_REASON_HDMI, + WAKE_REASON_DISPLAY_GROUP_ADDED, + WAKE_REASON_DISPLAY_GROUP_TURNED_ON, }) @Retention(RetentionPolicy.SOURCE) public @interface WakeReason{} /** + * @hide + */ + @IntDef(prefix = { "GO_TO_SLEEP_REASON_" }, value = { + GO_TO_SLEEP_REASON_APPLICATION, + GO_TO_SLEEP_REASON_DEVICE_ADMIN, + GO_TO_SLEEP_REASON_TIMEOUT, + GO_TO_SLEEP_REASON_LID_SWITCH, + GO_TO_SLEEP_REASON_POWER_BUTTON, + GO_TO_SLEEP_REASON_HDMI, + GO_TO_SLEEP_REASON_SLEEP_BUTTON, + GO_TO_SLEEP_REASON_ACCESSIBILITY, + GO_TO_SLEEP_REASON_FORCE_SUSPEND, + GO_TO_SLEEP_REASON_INATTENTIVE, + GO_TO_SLEEP_REASON_QUIESCENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface GoToSleepReason{} + + /** * Wake up reason code: Waking for an unknown reason. * @hide */ @@ -589,6 +624,18 @@ public final class PowerManager { public static final int WAKE_REASON_LID = 9; /** + * Wake up reason code: Waking due to display group being added. + * @hide + */ + public static final int WAKE_REASON_DISPLAY_GROUP_ADDED = 10; + + /** + * Wake up reason code: Waking due to display group being powered on. + * @hide + */ + public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11; + + /** * Convert the wake reason to a string for debugging purposes. * @hide */ @@ -604,6 +651,8 @@ public final class PowerManager { case WAKE_REASON_WAKE_MOTION: return "WAKE_REASON_WAKE_MOTION"; case WAKE_REASON_HDMI: return "WAKE_REASON_HDMI"; case WAKE_REASON_LID: return "WAKE_REASON_LID"; + case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED"; + case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON"; default: return Integer.toString(wakeReason); } } @@ -1195,8 +1244,15 @@ public final class PowerManager { } } - /** - * Forces the device to go to sleep. + /** + * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} + * to turn off. + * + * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is + * turned on it will be turned off. If all displays are off as a result of this action the + * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT + * default display group} is already off then nothing will happen. + * * <p> * Overrides all the wake locks that are held. * This is what happens when the power key is pressed to turn off the screen. @@ -1219,7 +1275,14 @@ public final class PowerManager { } /** - * Forces the device to go to sleep. + * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} + * to turn off. + * + * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is + * turned on it will be turned off. If all displays are off as a result of this action the + * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT + * default display group} is already off then nothing will happen. + * * <p> * Overrides all the wake locks that are held. * This is what happens when the power key is pressed to turn off the screen. @@ -1249,9 +1312,15 @@ public final class PowerManager { } /** - * Forces the device to wake up from sleep. + * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} + * to turn on. + * + * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is + * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If + * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already + * on then nothing will happen. + * * <p> - * If the device is currently asleep, wakes it up, otherwise does nothing. * This is what happens when the power key is pressed to turn on the screen. * </p><p> * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. @@ -1274,9 +1343,15 @@ public final class PowerManager { } /** - * Forces the device to wake up from sleep. + * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} + * to turn on. + * + * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is + * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If + * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already + * on then nothing will happen. + * * <p> - * If the device is currently asleep, wakes it up, otherwise does nothing. * This is what happens when the power key is pressed to turn on the screen. * </p><p> * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. @@ -1303,9 +1378,13 @@ public final class PowerManager { } /** - * Forces the device to wake up from sleep. + * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on. + * + * <p>If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will + * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link + * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen. + * * <p> - * If the device is currently asleep, wakes it up, otherwise does nothing. * This is what happens when the power key is pressed to turn on the screen. * </p><p> * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index b13be9f4a91a..1189fdb0b04a 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -52,6 +52,7 @@ import android.os.strictmode.ResourceMismatchViolation; import android.os.strictmode.ServiceConnectionLeakedViolation; import android.os.strictmode.SqliteObjectLeakedViolation; import android.os.strictmode.UnbufferedIoViolation; +import android.os.strictmode.UnsafeIntentLaunchViolation; import android.os.strictmode.UntaggedSocketViolation; import android.os.strictmode.Violation; import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation; @@ -256,6 +257,7 @@ public final class StrictMode { DETECT_VM_NON_SDK_API_USAGE, DETECT_VM_IMPLICIT_DIRECT_BOOT, DETECT_VM_INCORRECT_CONTEXT_USE, + DETECT_VM_UNSAFE_INTENT_LAUNCH, PENALTY_GATHER, PENALTY_LOG, PENALTY_DIALOG, @@ -297,6 +299,8 @@ public final class StrictMode { private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11; /** @hide */ private static final int DETECT_VM_INCORRECT_CONTEXT_USE = 1 << 12; + /** @hide */ + private static final int DETECT_VM_UNSAFE_INTENT_LAUNCH = 1 << 13; /** @hide */ private static final int DETECT_VM_ALL = 0x0000ffff; @@ -854,6 +858,7 @@ public final class StrictMode { * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and * other closable objects but will likely expand in future releases. */ + @SuppressWarnings("AndroidFrameworkCompatChange") public @NonNull Builder detectAll() { detectLeakedSqlLiteObjects(); @@ -885,6 +890,9 @@ public final class StrictMode { if (targetSdk >= Build.VERSION_CODES.R) { detectIncorrectContextUse(); } + if (targetSdk >= Build.VERSION_CODES.S) { + detectUnsafeIntentLaunch(); + } // TODO: Decide whether to detect non SDK API usage beyond a certain API level. // TODO: enable detectImplicitDirectBoot() once system is less noisy @@ -1067,6 +1075,59 @@ public final class StrictMode { } /** + * Detect when your app launches an {@link Intent} which originated + * from outside your app. + * <p> + * Violations may indicate security vulnerabilities in the design of + * your app, where a malicious app could trick you into granting + * {@link Uri} permissions or launching unexported components. Here + * are some typical design patterns that can be used to safely + * resolve these violations: + * <ul> + * <li>The ideal approach is to migrate to using a + * {@link android.app.PendingIntent}, which ensures that your launch is + * performed using the identity of the original creator, completely + * avoiding the security issues described above. + * <li>If using a {@link android.app.PendingIntent} isn't feasible, an + * alternative approach is to create a brand new {@link Intent} and + * carefully copy only specific values from the original + * {@link Intent} after careful validation. + * </ul> + * <p> + * Note that this <em>may</em> detect false-positives if your app + * sends itself an {@link Intent} which is first routed through the + * OS, such as using {@link Intent#createChooser}. In these cases, + * careful inspection is required to determine if the return point + * into your app is appropriately protected with a signature + * permission or marked as unexported. If the return point is not + * protected, your app is likely vulnerable to malicious apps. + * + * @see Context#startActivity(Intent) + * @see Context#startService(Intent) + * @see Context#bindService(Intent, ServiceConnection, int) + * @see Context#sendBroadcast(Intent) + * @see android.app.Activity#setResult(int, Intent) + */ + public @NonNull Builder detectUnsafeIntentLaunch() { + return enable(DETECT_VM_UNSAFE_INTENT_LAUNCH); + } + + /** + * Permit your app to launch any {@link Intent} which originated + * from outside your app. + * <p> + * Disabling this check is <em>strongly discouraged</em>, as + * violations may indicate security vulnerabilities in the design of + * your app, where a malicious app could trick you into granting + * {@link Uri} permissions or launching unexported components. + * + * @see #detectUnsafeIntentLaunch() + */ + public @NonNull Builder permitUnsafeIntentLaunch() { + return disable(DETECT_VM_UNSAFE_INTENT_LAUNCH); + } + + /** * Crashes the whole process on violation. This penalty runs at the end of all enabled * penalties so you'll still get your logging or other violations before the process * dies. @@ -2115,6 +2176,11 @@ public final class StrictMode { } /** @hide */ + public static boolean vmUnsafeIntentLaunchEnabled() { + return (sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 0; + } + + /** @hide */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack)); } @@ -2221,6 +2287,11 @@ public final class StrictMode { } } + /** @hide */ + public static void onUnsafeIntentLaunch(Intent intent) { + onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent)); + } + /** Assume locked until we hear otherwise */ private static volatile boolean sUserKeyUnlocked = false; diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java index 08e358f9094e..49bf08461016 100644 --- a/core/java/android/os/SystemBatteryConsumer.java +++ b/core/java/android/os/SystemBatteryConsumer.java @@ -41,7 +41,7 @@ public class SystemBatteryConsumer extends BatteryConsumer implements Parcelable // Reserved: APP DRAIN_TYPE_BLUETOOTH, DRAIN_TYPE_CAMERA, - DRAIN_TYPE_CELL, + DRAIN_TYPE_MOBILE_RADIO, DRAIN_TYPE_FLASHLIGHT, DRAIN_TYPE_IDLE, DRAIN_TYPE_MEMORY, @@ -59,7 +59,7 @@ public class SystemBatteryConsumer extends BatteryConsumer implements Parcelable public static final int DRAIN_TYPE_AMBIENT_DISPLAY = 0; public static final int DRAIN_TYPE_BLUETOOTH = 2; public static final int DRAIN_TYPE_CAMERA = 3; - public static final int DRAIN_TYPE_CELL = 4; + public static final int DRAIN_TYPE_MOBILE_RADIO = 4; public static final int DRAIN_TYPE_FLASHLIGHT = 5; public static final int DRAIN_TYPE_IDLE = 6; public static final int DRAIN_TYPE_MEMORY = 7; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ed60baf59c29..77183ac21d09 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4058,14 +4058,18 @@ public class UserManager { * the current user, then set the user as ephemeral so that it will be removed when it is * stopped. * + * @param evenWhenDisallowed when {@code true}, user is removed even if the caller user has the + * {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction + * * @return the {@link RemoveResult} code * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) { + public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId, + boolean evenWhenDisallowed) { try { - return mService.removeUserOrSetEphemeral(userId); + return mService.removeUserOrSetEphemeral(userId, evenWhenDisallowed); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java new file mode 100644 index 000000000000..891fb59326df --- /dev/null +++ b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java @@ -0,0 +1,53 @@ +/* + * 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.strictmode; + +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; + +/** + * Violation raised when your app launches an {@link Intent} which originated + * from outside your app. + * <p> + * Violations may indicate security vulnerabilities in the design of your app, + * where a malicious app could trick you into granting {@link Uri} permissions + * or launching unexported components. Here are some typical design patterns + * that can be used to safely resolve these violations: + * <ul> + * <li>The ideal approach is to migrate to using a {@link PendingIntent}, which + * ensures that your launch is performed using the identity of the original + * creator, completely avoiding the security issues described above. + * <li>If using a {@link PendingIntent} isn't feasible, an alternative approach + * is to create a brand new {@link Intent} and carefully copy only specific + * values from the original {@link Intent} after careful validation. + * </ul> + * <p> + * Note that this <em>may</em> detect false-positives if your app sends itself + * an {@link Intent} which is first routed through the OS, such as using + * {@link Intent#createChooser}. In these cases, careful inspection is required + * to determine if the return point into your app is appropriately protected + * with a signature permission or marked as unexported. If the return point is + * not protected, your app is likely vulnerable to malicious apps. + */ +public final class UnsafeIntentLaunchViolation extends Violation { + /** @hide */ + public UnsafeIntentLaunchViolation(@NonNull Intent intent) { + super("Launch of unsafe intent: " + intent); + } +} diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index c28b59ba9aac..db55e1c7afda 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -662,6 +662,11 @@ public class PermissionUsageHelper { // usage for that uid, keep it. Otherwise, remove it boolean isMostRecentForUid = true; for (int otherUsageNum = 0; otherUsageNum < rawUsages.size(); otherUsageNum++) { + // Do not compare this usage to itself + if (otherUsageNum == usageNum) { + continue; + } + OpUsage otherUsage = rawUsages.get(otherUsageNum); if (otherUsage.uid == usage.uid) { if (otherUsage.isRunning && !usage.isRunning) { diff --git a/core/java/android/se/OWNERS b/core/java/android/se/OWNERS index f1539dc55d59..5682fd3281f4 100644 --- a/core/java/android/se/OWNERS +++ b/core/java/android/se/OWNERS @@ -1,4 +1,5 @@ # Bug component: 456592 -cbrubaker@google.com -vishwath@google.com +zachoverflow@google.com +alisher@google.com +jackcwyu@google.com diff --git a/core/java/android/se/omapi/OWNERS b/core/java/android/se/omapi/OWNERS index f1539dc55d59..5682fd3281f4 100644 --- a/core/java/android/se/omapi/OWNERS +++ b/core/java/android/se/omapi/OWNERS @@ -1,4 +1,5 @@ # Bug component: 456592 -cbrubaker@google.com -vishwath@google.com +zachoverflow@google.com +alisher@google.com +jackcwyu@google.com diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java index a5c5c613e1f2..333af91ac872 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/core/java/android/se/omapi/SEService.java @@ -22,7 +22,10 @@ package android.se.omapi; +import android.annotation.BroadcastBehavior; import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -71,6 +74,28 @@ public final class SEService { } /** + * Broadcast Action: Intent to notify if the secure element state is changed. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(registeredOnly = true, protectedBroadcast = true) + public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED = + "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED"; + + /** + * Mandatory extra containing the reader name of the state changed secure element. + * + * @see Reader#getName() + */ + public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME"; + + /** + * Mandatory extra containing the connected state of the state changed secure element. + * + * True if the secure element is connected correctly, false otherwise. + */ + public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE"; + + /** * Listener object that allows the notification of the caller if this * SEService could be bound to the backend. */ diff --git a/core/java/android/service/attestation/IImpressionAttestationService.aidl b/core/java/android/service/attestation/IImpressionAttestationService.aidl index fcbc51febada..5ff8f1727602 100644 --- a/core/java/android/service/attestation/IImpressionAttestationService.aidl +++ b/core/java/android/service/attestation/IImpressionAttestationService.aidl @@ -39,7 +39,7 @@ oneway interface IImpressionAttestationService { * {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS}. * @param Callback The callback invoked to send back the impression token. */ - void generateImpressionToken(in String salt, in HardwareBuffer screenshot, in Rect bounds, + void generateImpressionToken(in byte[] salt, in HardwareBuffer screenshot, in Rect bounds, in String hashAlgorithm, in RemoteCallback callback); /** @@ -51,6 +51,6 @@ oneway interface IImpressionAttestationService { * @param impressionToken The token to verify that it was generated by the system. * @param callback The callback invoked to send back the verification status. */ - void verifyImpressionToken(in String salt, in ImpressionToken impressionToken, + void verifyImpressionToken(in byte[] salt, in ImpressionToken impressionToken, in RemoteCallback callback); } diff --git a/core/java/android/service/attestation/ImpressionAttestationService.java b/core/java/android/service/attestation/ImpressionAttestationService.java index 05ad5f02efe5..968d5331d237 100644 --- a/core/java/android/service/attestation/ImpressionAttestationService.java +++ b/core/java/android/service/attestation/ImpressionAttestationService.java @@ -100,7 +100,7 @@ public abstract class ImpressionAttestationService extends Service { * Returns null when the arguments sent are invalid. */ @Nullable - public abstract ImpressionToken onGenerateImpressionToken(@NonNull String salt, + public abstract ImpressionToken onGenerateImpressionToken(@NonNull byte[] salt, @NonNull HardwareBuffer screenshot, @NonNull Rect bounds, @NonNull String hashAlgorithm); @@ -109,16 +109,16 @@ public abstract class ImpressionAttestationService extends Service { * * @param salt The salt value to use when verifying the hmac. This should be the * same value that was passed to - * {@link #onGenerateImpressionToken(String, + * {@link #onGenerateImpressionToken(byte[], * HardwareBuffer, Rect, String)} to * generate the token. * @param impressionToken The token to verify that it was generated by the system. * @return true if the token can be verified that it was generated by the system. */ - public abstract boolean onVerifyImpressionToken(@NonNull String salt, + public abstract boolean onVerifyImpressionToken(@NonNull byte[] salt, @NonNull ImpressionToken impressionToken); - private void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds, + private void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds, String hashAlgorithm, RemoteCallback callback) { ImpressionToken impressionToken = onGenerateImpressionToken(salt, screenshot, bounds, hashAlgorithm); @@ -127,7 +127,7 @@ public abstract class ImpressionAttestationService extends Service { callback.sendResult(data); } - private void verifyImpressionToken(String salt, ImpressionToken impressionToken, + private void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken, RemoteCallback callback) { boolean verificationStatus = onVerifyImpressionToken(salt, impressionToken); final Bundle data = new Bundle(); @@ -138,7 +138,7 @@ public abstract class ImpressionAttestationService extends Service { private final class ImpressionAttestationServiceWrapper extends IImpressionAttestationService.Stub { @Override - public void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds, + public void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds, String hashAlgorithm, RemoteCallback callback) { mHandler.sendMessage( obtainMessage(ImpressionAttestationService::generateImpressionToken, @@ -147,7 +147,7 @@ public abstract class ImpressionAttestationService extends Service { } @Override - public void verifyImpressionToken(String salt, ImpressionToken impressionToken, + public void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken, RemoteCallback callback) { mHandler.sendMessage(obtainMessage(ImpressionAttestationService::verifyImpressionToken, ImpressionAttestationService.this, salt, impressionToken, callback)); diff --git a/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl b/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl new file mode 100644 index 000000000000..d9b403ca0609 --- /dev/null +++ b/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl @@ -0,0 +1,25 @@ +/* + * 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.service.resumeonreboot; + +import android.os.RemoteCallback; + +/** @hide */ +interface IResumeOnRebootService { + oneway void wrapSecret(in byte[] unwrappedBlob, in long lifeTimeInMillis, in RemoteCallback resultCallback); + oneway void unwrap(in byte[] wrappedBlob, in RemoteCallback resultCallback); +}
\ No newline at end of file diff --git a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java new file mode 100644 index 000000000000..4ebaa96f4be2 --- /dev/null +++ b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java @@ -0,0 +1,164 @@ +/* + * 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.service.resumeonreboot; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.ParcelableException; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import com.android.internal.os.BackgroundThread; + +import java.io.IOException; + +/** + * Base class for service that provides wrapping/unwrapping of the opaque blob needed for + * ResumeOnReboot operation. The package needs to provide a wrap/unwrap implementation for handling + * the opaque blob, that's secure even when on device keystore and clock is compromised. This can + * be achieved by using tamper-resistant hardware such as a secure element with a secure clock, or + * using a remote server to store and retrieve data and manage timing. + * + * <p>To extend this class, you must declare the service in your manifest file with the + * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE} permission, + * include an intent filter with the {@link #SERVICE_INTERFACE} action and mark the service as + * direct-boot aware. In addition, the package that contains the service must be granted + * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE}. + * For example:</p> + * <pre> + * <service android:name=".FooResumeOnRebootService" + * android:exported="true" + * android:priority="100" + * android:directBootAware="true" + * android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"> + * <intent-filter> + * <action android:name="android.service.resumeonreboot.ResumeOnRebootService" /> + * </intent-filter> + * </service> + * </pre> + * + * //TODO: Replace this with public link when available. + * + * @hide + * @see + * <a href="https://goto.google.com/server-based-ror">https://goto.google.com/server-based-ror</a> + */ +@SystemApi +public abstract class ResumeOnRebootService extends Service { + + /** + * The intent that the service must respond to. Add it to the intent filter of the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.resumeonreboot.ResumeOnRebootService"; + /** @hide */ + public static final String UNWRAPPED_BLOB_KEY = "unrwapped_blob_key"; + /** @hide */ + public static final String WRAPPED_BLOB_KEY = "wrapped_blob_key"; + /** @hide */ + public static final String EXCEPTION_KEY = "exception_key"; + + private final Handler mHandler = BackgroundThread.getHandler(); + + /** + * Implementation for wrapping the opaque blob used for resume-on-reboot prior to + * reboot. The service should not assume any structure of the blob to be wrapped. The + * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException} + * if it's unable to complete the action. + * + * @param blob The opaque blob with size on the order of 100 bytes. + * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the + * implementation and any attempt to unWrap the wrapped blob returned by + * this function after expiration should + * fail. + * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes. + * @throws IOException if the implementation is unable to wrap the blob successfully. + */ + @NonNull + public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis) + throws IOException; + + /** + * Implementation for unwrapping the wrapped blob used for resume-on-reboot after reboot. This + * operation would happen after reboot during direct boot mode (i.e before device is unlocked + * for the first time). The implementation should unwrap the wrapped blob in a reasonable time + * and returns the result or throw {@link IOException} if it's unable to complete the action + * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is + * stale. + * + * @param wrappedBlob The wrapped blob with size on the order of 100 bytes. + * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes. + * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully. + */ + @NonNull + public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException; + + private final android.service.resumeonreboot.IResumeOnRebootService mInterface = + new android.service.resumeonreboot.IResumeOnRebootService.Stub() { + + @Override + public void wrapSecret(byte[] unwrappedBlob, + @DurationMillisLong long lifeTimeInMillis, + RemoteCallback resultCallback) throws RemoteException { + mHandler.post(() -> { + try { + byte[] wrappedBlob = onWrap(unwrappedBlob, + lifeTimeInMillis); + Bundle bundle = new Bundle(); + bundle.putByteArray(WRAPPED_BLOB_KEY, wrappedBlob); + resultCallback.sendResult(bundle); + } catch (Throwable e) { + Bundle bundle = new Bundle(); + bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); + resultCallback.sendResult(bundle); + } + }); + } + + @Override + public void unwrap(byte[] wrappedBlob, RemoteCallback resultCallback) + throws RemoteException { + mHandler.post(() -> { + try { + byte[] unwrappedBlob = onUnwrap(wrappedBlob); + Bundle bundle = new Bundle(); + bundle.putByteArray(UNWRAPPED_BLOB_KEY, unwrappedBlob); + resultCallback.sendResult(bundle); + } catch (Throwable e) { + Bundle bundle = new Bundle(); + bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); + resultCallback.sendResult(bundle); + } + }); + } + }; + + @Nullable + @Override + public IBinder onBind(@Nullable Intent intent) { + return mInterface.asBinder(); + } +} diff --git a/core/java/android/service/textservice/OWNERS b/core/java/android/service/textservice/OWNERS index 10b8b7637431..0471e29a25cd 100644 --- a/core/java/android/service/textservice/OWNERS +++ b/core/java/android/service/textservice/OWNERS @@ -1,3 +1,3 @@ -# Bug component: 34867 +# Bug component: 816455 -include ../../inputmethodservice/OWNERS
\ No newline at end of file +include /services/core/java/com/android/server/textservices/OWNERS diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java index bd1b44ca18d0..e4925842ad85 100644 --- a/core/java/android/service/textservice/SpellCheckerService.java +++ b/core/java/android/service/textservice/SpellCheckerService.java @@ -16,11 +16,6 @@ package android.service.textservice; -import com.android.internal.textservice.ISpellCheckerService; -import com.android.internal.textservice.ISpellCheckerServiceCallback; -import com.android.internal.textservice.ISpellCheckerSession; -import com.android.internal.textservice.ISpellCheckerSessionListener; - import android.app.Service; import android.content.Intent; import android.os.Bundle; @@ -34,6 +29,11 @@ import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; +import com.android.internal.textservice.ISpellCheckerService; +import com.android.internal.textservice.ISpellCheckerServiceCallback; +import com.android.internal.textservice.ISpellCheckerSession; +import com.android.internal.textservice.ISpellCheckerSessionListener; + import java.lang.ref.WeakReference; import java.text.BreakIterator; import java.util.ArrayList; @@ -231,6 +231,18 @@ public abstract class SpellCheckerService extends Service { public Bundle getBundle() { return mInternalSession.getBundle(); } + + /** + * Returns result attributes supported for this session. + * + * <p>The session implementation should not set attributes that are not included in the + * return value of {@code getSupportedAttributes()} when creating {@link SuggestionsInfo}. + * + * @return The supported result attributes for this session + */ + public @SuggestionsInfo.ResultAttrs int getSupportedAttributes() { + return mInternalSession.getSupportedAttributes(); + } } // Preventing from exposing ISpellCheckerSession.aidl, create an internal class. @@ -239,13 +251,16 @@ public abstract class SpellCheckerService extends Service { private final Session mSession; private final String mLocale; private final Bundle mBundle; + private final @SuggestionsInfo.ResultAttrs int mSupportedAttributes; public InternalISpellCheckerSession(String locale, ISpellCheckerSessionListener listener, - Bundle bundle, Session session) { + Bundle bundle, Session session, + @SuggestionsInfo.ResultAttrs int supportedAttributes) { mListener = listener; mSession = session; mLocale = locale; mBundle = bundle; + mSupportedAttributes = supportedAttributes; session.setInternalISpellCheckerSession(this); } @@ -303,6 +318,10 @@ public abstract class SpellCheckerService extends Service { public Bundle getBundle() { return mBundle; } + + public @SuggestionsInfo.ResultAttrs int getSupportedAttributes() { + return mSupportedAttributes; + } } private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub { @@ -323,11 +342,14 @@ public abstract class SpellCheckerService extends Service { * {@link Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} and * {@link Session#onGetSuggestions(TextInfo, int)} * @param bundle bundle to be returned from {@link Session#getBundle()} + * @param supportedAttributes A union of {@link SuggestionsInfo} attributes that the spell + * checker can set in the spell checking results. * @param callback IPC channel to return the result to the caller in an asynchronous manner */ @Override public void getISpellCheckerSession( String locale, ISpellCheckerSessionListener listener, Bundle bundle, + @SuggestionsInfo.ResultAttrs int supportedAttributes, ISpellCheckerServiceCallback callback) { final SpellCheckerService service = mInternalServiceRef.get(); final InternalISpellCheckerSession internalSession; @@ -337,8 +359,8 @@ public abstract class SpellCheckerService extends Service { internalSession = null; } else { final Session session = service.createSession(); - internalSession = - new InternalISpellCheckerSession(locale, listener, bundle, session); + internalSession = new InternalISpellCheckerSession( + locale, listener, bundle, session, supportedAttributes); session.onCreate(); } try { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 63ee927ded3d..aa05787359eb 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -225,7 +225,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject, int transformHint); private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken, - IBinder focusedToken, int displayId); + String windowName, IBinder focusedToken, String focusedWindowName, int displayId); private static native void nativeSetFrameTimelineVsync(long transactionObj, long frameTimelineVsyncId); private static native void nativeAddJankDataListener(long nativeListener, @@ -3282,8 +3282,10 @@ public final class SurfaceControl implements Parcelable { * * @hide */ - public Transaction setFocusedWindow(@NonNull IBinder token, int displayId) { - nativeSetFocusedWindow(mNativeObject, token, null /* focusedToken */, displayId); + public Transaction setFocusedWindow(@NonNull IBinder token, String windowName, + int displayId) { + nativeSetFocusedWindow(mNativeObject, token, windowName, + null /* focusedToken */, null /* focusedWindowName */, displayId); return this; } @@ -3298,9 +3300,12 @@ public final class SurfaceControl implements Parcelable { * @hide */ public Transaction requestFocusTransfer(@NonNull IBinder token, + String windowName, @NonNull IBinder focusedToken, + String focusedWindowName, int displayId) { - nativeSetFocusedWindow(mNativeObject, token, focusedToken, displayId); + nativeSetFocusedWindow(mNativeObject, token, windowName, focusedToken, + focusedWindowName, displayId); return this; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 7ac57b5e0421..f603ef7901c7 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1193,11 +1193,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void setBufferSize(Transaction transaction) { if (mUseBlastAdapter) { - mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, - mSurfaceHeight); + mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat); } else { - transaction.setBufferSize(mSurfaceControl, mSurfaceWidth, - mSurfaceHeight); + transaction.setBufferSize(mSurfaceControl, mSurfaceWidth, mSurfaceHeight); } } @@ -1241,15 +1239,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall .setName(name + "(BLAST)") .setLocalOwnerView(this) .setBufferSize(mSurfaceWidth, mSurfaceHeight) - .setFormat(mFormat) .setParent(mSurfaceControl) .setFlags(mSurfaceFlags) .setHidden(false) .setBLASTLayer() .setCallsite("SurfaceView.updateSurface") .build(); - mBlastBufferQueue = new BLASTBufferQueue(name, - mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, true /* TODO */); + mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, + mSurfaceHeight, mFormat, true /* TODO */); } else { previousSurfaceControl = mSurfaceControl; mSurfaceControl = builder diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d6949623f377..96d7304d6968 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1852,20 +1852,22 @@ public final class ViewRootImpl implements ViewParent, return mBoundsLayer; } - Surface getOrCreateBLASTSurface(int width, int height) { + Surface getOrCreateBLASTSurface(int width, int height, + @Nullable WindowManager.LayoutParams params) { if (!mSurfaceControl.isValid()) { return null; } + int format = params == null ? PixelFormat.TRANSLUCENT : params.format; Surface ret = null; if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue(mTag, - mSurfaceControl, width, height, mEnableTripleBuffering); + mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height, + format, mEnableTripleBuffering); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); } else { - mBlastBufferQueue.update(mSurfaceControl, width, height); + mBlastBufferQueue.update(mSurfaceControl, width, height, format); } return ret; @@ -7608,8 +7610,8 @@ public final class ViewRootImpl implements ViewParent, if (!useBLAST()) { mSurface.copyFrom(mSurfaceControl); } else { - final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, - mSurfaceSize.y); + final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y, + params); // If blastSurface == null that means it hasn't changed since the last time we // called. In this situation, avoid calling transferFrom as we would then // inc the generation ID and cause EGL resources to be recreated. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c2d990a40fe4..8b8645cf6c4f 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2151,9 +2151,8 @@ public interface WindowManager extends ViewManager { * visible window. * @hide */ - @SystemApi @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY) - public static final int SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008; + public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008; /** In a multiuser system if this flag is set and the owner is a system process then this * window will appear on all user screens. This overrides the default behavior of window @@ -2352,7 +2351,6 @@ public interface WindowManager extends ViewManager { @IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = { SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, SYSTEM_FLAG_SHOW_FOR_ALL_USERS, - SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY, }) public @interface SystemFlags {} @@ -2386,7 +2384,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME, PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, - SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY, + PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, }) public @interface PrivateFlags {} @@ -2501,9 +2499,9 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, name = "INTERCEPT_GLOBAL_DRAG_AND_DROP"), @ViewDebug.FlagToString( - mask = SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY, - equals = SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY, - name = "SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY") + mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, + equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, + name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY") }) @PrivateFlags @TestApi @@ -3375,6 +3373,37 @@ public interface WindowManager extends ViewManager { } /** + * When set on {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows they stay visible, + * even if {@link LayoutParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS} is set for + * another visible window. + * @hide + */ + @SystemApi + @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY) + public void setSystemApplicationOverlay(boolean isSystemApplicationOverlay) { + if (isSystemApplicationOverlay) { + privateFlags |= PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY; + } else { + privateFlags &= ~PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY; + } + } + + /** + * Returns if this window is marked as being a system application overlay. + * @see LayoutParams#setSystemApplicationOverlay(boolean) + * + * <p>Note: the owner of the window must hold + * {@link android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY} for this to have any + * effect. + * @hide + */ + @SystemApi + public boolean isSystemApplicationOverlay() { + return (privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY) + == PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY; + } + + /** * @return the insets types that this window is avoiding overlapping. */ public @InsetsType int getFitInsetsTypes() { diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index dd55f049b364..7668d80d3cb1 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.os.PowerManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -108,6 +109,27 @@ public interface WindowManagerPolicyConstants { void onPointerEvent(MotionEvent motionEvent); } + @IntDef(prefix = { "OFF_BECAUSE_OF_" }, value = { + OFF_BECAUSE_OF_ADMIN, + OFF_BECAUSE_OF_USER, + OFF_BECAUSE_OF_TIMEOUT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OffReason{} + + static @OffReason int translateSleepReasonToOffReason( + @PowerManager.GoToSleepReason int reason) { + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + return OFF_BECAUSE_OF_ADMIN; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + case PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE: + return OFF_BECAUSE_OF_TIMEOUT; + default: + return OFF_BECAUSE_OF_USER; + } + } + /** Screen turned off because of a device admin */ int OFF_BECAUSE_OF_ADMIN = 1; /** Screen turned off because of power button */ @@ -137,6 +159,23 @@ public interface WindowManagerPolicyConstants { } } + static @OnReason int translateWakeReasonToOnReason(@PowerManager.WakeReason int reason) { + switch (reason) { + case PowerManager.WAKE_REASON_POWER_BUTTON: + case PowerManager.WAKE_REASON_PLUGGED_IN: + case PowerManager.WAKE_REASON_GESTURE: + case PowerManager.WAKE_REASON_CAMERA_LAUNCH: + case PowerManager.WAKE_REASON_WAKE_KEY: + case PowerManager.WAKE_REASON_WAKE_MOTION: + case PowerManager.WAKE_REASON_LID: + return ON_BECAUSE_OF_USER; + case PowerManager.WAKE_REASON_APPLICATION: + return ON_BECAUSE_OF_APPLICATION; + default: + return ON_BECAUSE_OF_UNKNOWN; + } + } + /** Screen turned on because of a user-initiated action. */ int ON_BECAUSE_OF_USER = 1; /** Screen turned on because of an application request or event */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0a4b784d0478..5140c09dc323 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2926,7 +2926,10 @@ public final class InputMethodManager { ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; try { - mService.showInputMethodPickerFromSystem(mClient, mode, displayId); + final Completable.Void value = Completable.createVoid(); + mService.showInputMethodPickerFromSystem( + mClient, mode, displayId, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2934,7 +2937,10 @@ public final class InputMethodManager { private void showInputMethodPickerLocked() { try { - mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO); + final Completable.Void value = Completable.createVoid(); + mService.showInputMethodPickerFromClient( + mClient, SHOW_IM_PICKER_MODE_AUTO, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2970,7 +2976,10 @@ public final class InputMethodManager { */ public void showInputMethodAndSubtypeEnabler(String imiId) { try { - mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId); + final Completable.Void value = Completable.createVoid(); + mService.showInputMethodAndSubtypeEnablerFromClient( + mClient, imiId, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3132,7 +3141,10 @@ public final class InputMethodManager { matrixValues = new float[9]; matrix.getValues(matrixValues); } - mService.reportActivityView(mClient, childDisplayId, matrixValues); + final Completable.Void value = Completable.createVoid(); + mService.reportActivityView( + mClient, childDisplayId, matrixValues, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/textservice/OWNERS b/core/java/android/view/textservice/OWNERS index 582be8dc4594..0471e29a25cd 100644 --- a/core/java/android/view/textservice/OWNERS +++ b/core/java/android/view/textservice/OWNERS @@ -1,3 +1,3 @@ -# Bug component: 34867 +# Bug component: 816455 -include ../inputmethod/OWNERS +include /services/core/java/com/android/server/textservices/OWNERS diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java index 1301c49c2ae0..775a6bdacd01 100644 --- a/core/java/android/view/textservice/SuggestionsInfo.java +++ b/core/java/android/view/textservice/SuggestionsInfo.java @@ -16,11 +16,15 @@ package android.view.textservice; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.ArrayUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This class contains a metadata of suggestions from the text service */ @@ -28,6 +32,22 @@ public final class SuggestionsInfo implements Parcelable { private static final String[] EMPTY = ArrayUtils.emptyArray(String.class); /** + * An internal annotation to indicate that one ore more combinations of + * <code>RESULT_ATTR_</code> flags are expected. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "RESULT_ATTR_" }, value = { + RESULT_ATTR_IN_THE_DICTIONARY, + RESULT_ATTR_LOOKS_LIKE_TYPO, + RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS, + RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR, + RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS, + }) + public @interface ResultAttrs {} + + /** * Flag of the attributes of the suggestions that can be obtained by * {@link #getSuggestionsAttributes}: this tells that the requested word was found * in the dictionary in the text service. @@ -63,7 +83,7 @@ public final class SuggestionsInfo implements Parcelable { */ public static final int RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS = 0x0010; - private final int mSuggestionsAttributes; + private final @ResultAttrs int mSuggestionsAttributes; private final String[] mSuggestions; private final boolean mSuggestionsAvailable; private int mCookie; @@ -85,8 +105,8 @@ public final class SuggestionsInfo implements Parcelable { * @param cookie the cookie of the input TextInfo * @param sequence the cookie of the input TextInfo */ - public SuggestionsInfo( - int suggestionsAttributes, String[] suggestions, int cookie, int sequence) { + public SuggestionsInfo(@ResultAttrs int suggestionsAttributes, String[] suggestions, int cookie, + int sequence) { if (suggestions == null) { mSuggestions = EMPTY; mSuggestionsAvailable = false; @@ -152,7 +172,7 @@ public final class SuggestionsInfo implements Parcelable { * in its dictionary or not and whether the spell checker has confident suggestions for the * word or not. */ - public int getSuggestionsAttributes() { + public @ResultAttrs int getSuggestionsAttributes() { return mSuggestionsAttributes; } diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index 578ed8c948e2..0a1aea38dd58 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -18,6 +18,7 @@ package android.view.textservice; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemService; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; @@ -139,21 +140,58 @@ public final class TextServicesManager { } /** - * Get a spell checker session for the specified spell checker - * @param locale the locale for the spell checker. If {@code locale} is null and - * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be - * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, - * the locale specified in Settings will be returned only when it is same as {@code locale}. - * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is - * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * Get a spell checker session from the spell checker. + * + * <p>{@link SuggestionsInfo#RESULT_ATTR_IN_THE_DICTIONARY}, + * {@link SuggestionsInfo#RESULT_ATTR_LOOKS_LIKE_TYPO}, and + * {@link SuggestionsInfo#RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS} will be passed to the spell + * checker as supported attributes. + * + * @see #newSpellCheckerSession(Bundle, Locale, SpellCheckerSessionListener, boolean, int) + * @param bundle A bundle to pass to the spell checker. + * @param locale The locale for the spell checker. + * @param listener A spell checker session lister for getting results from the spell checker. + * @param referToSpellCheckerLanguageSettings If true, the session for one of enabled + * languages in settings will be used. + * @return A spell checker session from the spell checker. + */ + @Nullable + public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle, + @Nullable Locale locale, + @NonNull SpellCheckerSessionListener listener, + boolean referToSpellCheckerLanguageSettings) { + return newSpellCheckerSession(bundle, locale, listener, referToSpellCheckerLanguageSettings, + SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY + | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO + | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS); + } + + /** + * Get a spell checker session from the spell checker. + * + * <p>If {@code locale} is null and {@code referToSpellCheckerLanguageSettings} is true, the + * locale specified in Settings will be used. If {@code locale} is not null and + * {@code referToSpellCheckerLanguageSettings} is true, the locale specified in Settings will be + * returned only when it is same as {@code locale}. + * Exceptionally, when {@code referToSpellCheckerLanguageSettings} is true and {@code locale} is + * language only (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be * selected. - * @param listener a spell checker session lister for getting results from a spell checker. - * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled - * languages in settings will be returned. - * @return the spell checker session of the spell checker + * + * @param bundle A bundle to pass to the spell checker. + * @param locale The locale for the spell checker. + * @param listener A spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings If true, the session for one of enabled + * languages in settings will be used. + * @param supportedAttributes A union of {@link SuggestionsInfo} attributes that the spell + * checker can set in the spell checking results. + * @return The spell checker session of the spell checker. */ - public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, - SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { + @Nullable + public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle, + @SuppressLint("UseIcu") @Nullable Locale locale, + @NonNull SpellCheckerSessionListener listener, + @SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings, + @SuppressLint("ListenerLast") @SuggestionsInfo.ResultAttrs int supportedAttributes) { if (listener == null) { throw new NullPointerException(); } @@ -210,7 +248,7 @@ public final class TextServicesManager { try { mService.getSpellCheckerService(mUserId, sci.getId(), subtypeInUse.getLocale(), session.getTextServicesSessionListener(), - session.getSpellCheckerSessionListener(), bundle); + session.getSpellCheckerSessionListener(), bundle, supportedAttributes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index b06fa1a058d4..97d98fd8ec59 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -129,7 +129,11 @@ public class SpellChecker implements SpellCheckerSessionListener { mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession( null /* Bundle not currently used by the textServicesManager */, mCurrentLocale, this, - false /* means any available languages from current spell checker */); + false /* means any available languages from current spell checker */, + SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY + | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO + | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR + | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS); mIsSentenceSpellCheckSupported = true; } diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java index e9e39db90437..21131738cede 100644 --- a/core/java/com/android/internal/inputmethod/CallbackUtils.java +++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java @@ -205,14 +205,14 @@ public final class CallbackUtils { * A utility method using given {@link IVoidResultCallback} to callback the result. * * @param callback {@link IVoidResultCallback} to be called back. - * @param resultSupplier the supplier from which the result is provided. + * @param runnable to execute the given method */ public static void onResult(@NonNull IVoidResultCallback callback, - @NonNull Supplier<Void> resultSupplier) { + @NonNull Runnable runnable) { Throwable exception = null; try { - resultSupplier.get(); + runnable.run(); } catch (Throwable throwable) { exception = throwable; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index f105320d0353..ee3c12c61771 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -860,14 +860,11 @@ public class BatteryStatsImpl extends BatteryStats { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected int mScreenState = Display.STATE_UNKNOWN; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - protected StopwatchTimer mScreenOnTimer; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - protected StopwatchTimer mScreenDozeTimer; + StopwatchTimer mScreenOnTimer; + StopwatchTimer mScreenDozeTimer; int mScreenBrightnessBin = -1; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - protected final StopwatchTimer[] mScreenBrightnessTimer = + final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS]; boolean mPretendScreenOff; @@ -912,8 +909,7 @@ public class BatteryStatsImpl extends BatteryStats { int mUsbDataState = USB_DATA_UNKNOWN; int mGpsSignalQualityBin = -1; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - protected final StopwatchTimer[] mGpsSignalQualityTimer = + final StopwatchTimer[] mGpsSignalQualityTimer = new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS]; int mPhoneSignalStrengthBin = -1; @@ -929,6 +925,7 @@ public class BatteryStatsImpl extends BatteryStats { final LongSamplingCounter[] mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; + final LongSamplingCounter[] mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; @@ -948,8 +945,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * The Bluetooth controller activity (time in tx, rx, idle, and power consumed) for the device. */ - @VisibleForTesting - protected ControllerActivityCounterImpl mBluetoothActivity; + ControllerActivityCounterImpl mBluetoothActivity; /** * The Modem controller activity (time in tx, rx, idle, and power consumed) for the device. @@ -993,8 +989,7 @@ public class BatteryStatsImpl extends BatteryStats { StopwatchTimer mWifiActiveTimer; int mBluetoothScanNesting; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - protected StopwatchTimer mBluetoothScanTimer; + StopwatchTimer mBluetoothScanTimer; int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; long mMobileRadioActiveStartTimeMs; @@ -10718,6 +10713,31 @@ public class BatteryStatsImpl extends BatteryStats { mHandler = new MyHandler(handler.getLooper()); mConstants = new Constants(mHandler); mStartCount++; + initTimersAndCounters(); + mOnBattery = mOnBatteryInternal = false; + long uptimeUs = mClocks.uptimeMillis() * 1000; + long realtimeUs = mClocks.elapsedRealtime() * 1000; + initTimes(uptimeUs, realtimeUs); + mStartPlatformVersion = mEndPlatformVersion = Build.ID; + mDischargeStartLevel = 0; + mDischargeUnplugLevel = 0; + mDischargePlugLevel = -1; + mDischargeCurrentLevel = 0; + mCurrentBatteryLevel = 0; + initDischarge(realtimeUs); + clearHistoryLocked(); + updateDailyDeadlineLocked(); + mPlatformIdleStateCallback = cb; + mMeasuredEnergyRetriever = energyStatsCb; + mUserInfoProvider = userInfoProvider; + + // Notify statsd that the system is initially not in doze. + mDeviceIdleMode = DEVICE_IDLE_MODE_OFF; + FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode); + } + + @VisibleForTesting + protected void initTimersAndCounters() { mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase); mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { @@ -10789,26 +10809,6 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase); - mOnBattery = mOnBatteryInternal = false; - long uptimeUs = mClocks.uptimeMillis() * 1000; - long realtimeUs = mClocks.elapsedRealtime() * 1000; - initTimes(uptimeUs, realtimeUs); - mStartPlatformVersion = mEndPlatformVersion = Build.ID; - mDischargeStartLevel = 0; - mDischargeUnplugLevel = 0; - mDischargePlugLevel = -1; - mDischargeCurrentLevel = 0; - mCurrentBatteryLevel = 0; - initDischarge(realtimeUs); - clearHistoryLocked(); - updateDailyDeadlineLocked(); - mPlatformIdleStateCallback = cb; - mMeasuredEnergyRetriever = energyStatsCb; - mUserInfoProvider = userInfoProvider; - - // Notify statsd that the system is initially not in doze. - mDeviceIdleMode = DEVICE_IDLE_MODE_OFF; - FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode); } @UnsupportedAppUsage @@ -11623,7 +11623,8 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mModemNetworkLock") private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1); - private NetworkStats readNetworkStatsLocked(String[] ifaces) { + @VisibleForTesting + protected NetworkStats readNetworkStatsLocked(String[] ifaces) { try { if (!ArrayUtils.isEmpty(ifaces)) { INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( @@ -11922,7 +11923,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * Distribute Cell radio energy info and network traffic to apps. */ - public void updateMobileRadioState(@Nullable final ModemActivityInfo activityInfo, + public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo, long elapsedRealtimeMs, long uptimeMs) { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating mobile radio stats with " + activityInfo); diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java index 790d2e51e38d..e3bd64d77e9b 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -15,7 +15,12 @@ */ package com.android.internal.os; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.telephony.CellSignalStrength; import android.util.Log; @@ -26,103 +31,146 @@ import java.util.List; public class MobileRadioPowerCalculator extends PowerCalculator { private static final String TAG = "MobRadioPowerCalculator"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; - private final double mPowerRadioOn; - private final double[] mPowerBins = new double[CellSignalStrength.getNumSignalStrengthLevels()]; - private final double mPowerScan; - private BatteryStats mStats; - private long mTotalAppMobileActiveMs = 0; - /** - * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. - */ - private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) { - final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system - final double MOBILE_POWER = mPowerRadioOn / 3600; + private static final int NUM_SIGNAL_STRENGTH_LEVELS = + CellSignalStrength.getNumSignalStrengthLevels(); - final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, - statsType); - final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, - statsType); - final long mobileData = mobileRx + mobileTx; + private final UsageBasedPowerEstimator mActivePowerEstimator; + private final UsageBasedPowerEstimator[] mIdlePowerEstimators = + new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; + private final UsageBasedPowerEstimator mScanPowerEstimator; - final long radioDataUptimeMs = - mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; - final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) - ? (mobileData / (double) radioDataUptimeMs) - : (((double) MOBILE_BPS) / 8 / 2048); - return (MOBILE_POWER / mobilePps) / (60 * 60); + private static class PowerAndDuration { + public long durationMs; + public double powerMah; + public long totalAppDurationMs; + public long signalDurationMs; + public long noCoverageDurationMs; } public MobileRadioPowerCalculator(PowerProfile profile) { - double temp = + // Power consumption when radio is active + double powerRadioActiveMa = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1); - if (temp != -1) { - mPowerRadioOn = temp; - } else { + if (powerRadioActiveMa == -1) { double sum = 0; sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); - for (int i = 0; i < mPowerBins.length; i++) { + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); } - mPowerRadioOn = sum / (mPowerBins.length + 1); + powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1); } - temp = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1); - if (temp != -1) { - for (int i = 0; i < mPowerBins.length; i++) { - mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); + mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa); + + // Power consumption when radio is on, but idle + if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) { + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { + mIdlePowerEstimators[i] = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i)); } } else { double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE); - mPowerBins[0] = idle * 25 / 180; - for (int i = 1; i < mPowerBins.length; i++) { - mPowerBins[i] = Math.max(1, idle / 256); + + // Magical calculations preserved for historical compatibility + mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180); + for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { + mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256)); } } - mPowerScan = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0); + mScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0)); + } + + @Override + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query, + SparseArray<UserHandle> asUsers) { + + PowerAndDuration total = new PowerAndDuration(); + + final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED); + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + final BatteryStats.Uid uid = app.getBatteryStatsUid(); + calculateApp(app, uid, powerPerPacketMah, total); + } + + calculateRemaining(total, batteryStats, rawRealtimeUs); + + if (total.powerMah != 0) { + builder.getOrCreateSystemBatteryConsumerBuilder( + SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO) + .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO, + total.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, total.powerMah); + } + } + + private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, + double powerPerPacketMah, PowerAndDuration total) { + final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED); + total.totalAppDurationMs += radioActiveDurationMs; + + final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs); + + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO, + radioActiveDurationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah); } @Override public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - mStats = batteryStats; - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs, + statsType); + PowerAndDuration total = new PowerAndDuration(); + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + final BatteryStats.Uid u = app.uidObj; + calculateApp(app, u, statsType, mobilePowerPerPacket, total); + } + } BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); - calculateRemaining(radio, mStats, rawRealtimeUs, rawUptimeUs, statsType); - radio.sumPower(); + calculateRemaining(total, batteryStats, rawRealtimeUs); + if (total.powerMah != 0) { + if (total.signalDurationMs != 0) { + radio.noCoveragePercent = + total.noCoverageDurationMs * 100.0 / total.signalDurationMs; + } + radio.mobileActive = total.durationMs; + radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType); + radio.mobileRadioPowerMah = total.powerMah; + radio.sumPower(); + } if (radio.totalPowerMah > 0) { sippers.add(radio); } } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { + private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, + double powerPerPacketMah, PowerAndDuration total) { + app.mobileActive = calculateDuration(u, statsType); + app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive); + total.totalAppDurationMs += app.mobileActive; + // Add cost of mobile traffic. app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, statsType); - app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000; app.mobileActiveCount = u.getMobileRadioActiveCount(statsType); app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA, statsType); - if (app.mobileActive > 0) { - // We are tracking when the radio is up, so can use the active time to - // determine power use. - mTotalAppMobileActiveMs += app.mobileActive; - app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000 * 60 * 60); - } else { - // We are not tracking when the radio is up, so must approximate power use - // based on the number of packets. - app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets) - * getMobilePowerPerPacket(rawRealtimeUs, statsType); - } if (DEBUG && app.mobileRadioPowerMah != 0) { Log.d(TAG, "UID " + u.getUid() + ": mobile packets " + (app.mobileRxPackets + app.mobileTxPackets) @@ -131,52 +179,80 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } } - private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - double power = 0; + private long calculateDuration(BatteryStats.Uid u, int statsType) { + return u.getMobileRadioActiveTime(statsType) / 1000; + } + + private double calculatePower(BatteryStats.Uid u, double powerPerPacketMah, + long radioActiveDurationMs) { + if (radioActiveDurationMs > 0) { + // We are tracking when the radio is up, so can use the active time to + // determine power use. + return mActivePowerEstimator.calculatePower(radioActiveDurationMs); + } else { + // We are not tracking when the radio is up, so must approximate power use + // based on the number of packets. + final long mobileRxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_MOBILE_RX_DATA, + BatteryStats.STATS_SINCE_CHARGED); + final long mobileTxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_MOBILE_TX_DATA, + BatteryStats.STATS_SINCE_CHARGED); + return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah; + } + } + + private void calculateRemaining(MobileRadioPowerCalculator.PowerAndDuration total, + BatteryStats batteryStats, long rawRealtimeUs) { long signalTimeMs = 0; - long noCoverageTimeMs = 0; - for (int i = 0; i < mPowerBins.length; i++) { - long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType) - / 1000; - final double p = (strengthTimeMs * mPowerBins[i]) / (60 * 60 * 1000); + double powerMah = 0; + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { + long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + final double p = mIdlePowerEstimators[i].calculatePower(strengthTimeMs); if (DEBUG && p != 0) { Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + formatCharge(p)); } - power += p; + powerMah += p; signalTimeMs += strengthTimeMs; if (i == 0) { - noCoverageTimeMs = strengthTimeMs; + total.noCoverageDurationMs = strengthTimeMs; } } - final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType) - / 1000; - final double p = (scanningTimeMs * mPowerScan) / (60 * 60 * 1000); + final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + final double p = mScanPowerEstimator.calculatePower(scanningTimeMs); if (DEBUG && p != 0) { Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(p)); } - power += p; - long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; - long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs; + powerMah += p; + long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs; if (remainingActiveTimeMs > 0) { - power += (mPowerRadioOn * remainingActiveTimeMs) / (1000 * 60 * 60); - } - - if (power != 0) { - if (signalTimeMs != 0) { - app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; - } - app.mobileActive = remainingActiveTimeMs; - app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType); - app.mobileRadioPowerMah = power; + powerMah += mActivePowerEstimator.calculatePower(remainingActiveTimeMs); } + total.durationMs = radioActiveTimeMs; + total.powerMah = powerMah; + total.signalDurationMs = signalTimeMs; } - @Override - public void reset() { - mTotalAppMobileActiveMs = 0; - mStats = null; + /** + * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio. + */ + private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) { + final long radioDataUptimeMs = + stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; + final double mobilePower = mActivePowerEstimator.calculatePower(radioDataUptimeMs); + + final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, + statsType); + final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, + statsType); + final long mobilePackets = mobileRx + mobileTx; + + return mobilePackets != 0 ? mobilePower / mobilePackets : 0; } } diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 54f31f9199cb..b01e4a83a438 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -42,26 +42,29 @@ oneway interface IKeyguardService { /** * Called when the device has started going to sleep. * - * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN}, - * or {@link #OFF_BECAUSE_OF_TIMEOUT}. + * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason + * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. */ - void onStartedGoingToSleep(int reason); + void onStartedGoingToSleep(int pmSleepReason); /** * Called when the device has finished going to sleep. * - * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN}, - * or {@link #OFF_BECAUSE_OF_TIMEOUT}. + * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason + * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. * @param cameraGestureTriggered whether the camera gesture was triggered between * {@link #onStartedGoingToSleep} and this method; if it's been * triggered, we shouldn't lock the device. */ - void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered); + void onFinishedGoingToSleep(int pmSleepReason, boolean cameraGestureTriggered); /** * Called when the device has started waking up. + + * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up, + * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. */ - void onStartedWakingUp(); + void onStartedWakingUp(int pmWakeReason); /** * Called when the device has finished waking up. diff --git a/core/java/com/android/internal/textservice/ISpellCheckerService.aidl b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl index 6a2596458667..295107bb7179 100644 --- a/core/java/com/android/internal/textservice/ISpellCheckerService.aidl +++ b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl @@ -39,9 +39,11 @@ oneway interface ISpellCheckerService { * {@link android.service.textservice.SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} and * {@link android.service.textservice.SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} * @param bundle bundle to be returned from {@link android.service.textservice.SpellCheckerService.Session#getBundle()} + * @param supportedAttributes supported attributes to be returned from {@link android.service.textservice.SpellCheckerService.Session#getSupportedAttributes()} * @param callback IPC channel to return the result to the caller in an asynchronous manner */ void getISpellCheckerSession( String locale, ISpellCheckerSessionListener listener, in Bundle bundle, + int supportedAttributes, ISpellCheckerServiceCallback callback); } diff --git a/core/java/com/android/internal/textservice/ITextServicesManager.aidl b/core/java/com/android/internal/textservice/ITextServicesManager.aidl index 8022949d1728..dce67e7ca380 100644 --- a/core/java/com/android/internal/textservice/ITextServicesManager.aidl +++ b/core/java/com/android/internal/textservice/ITextServicesManager.aidl @@ -34,7 +34,7 @@ interface ITextServicesManager { boolean allowImplicitlySelectedSubtype); oneway void getSpellCheckerService(int userId, String sciId, in String locale, in ITextServicesSessionListener tsListener, - in ISpellCheckerSessionListener scListener, in Bundle bundle); + in ISpellCheckerSessionListener scListener, in Bundle bundle, int supportedAttributes); oneway void finishSpellCheckerService(int userId, in ISpellCheckerSessionListener listener); boolean isSpellCheckerEnabled(int userId); SpellCheckerInfo[] getEnabledSpellCheckers(int userId); diff --git a/core/java/com/android/internal/textservice/OWNERS b/core/java/com/android/internal/textservice/OWNERS new file mode 100644 index 000000000000..0471e29a25cd --- /dev/null +++ b/core/java/com/android/internal/textservice/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 816455 + +include /services/core/java/com/android/server/textservices/OWNERS diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 1fadfc5562da..b42404fb6d56 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -30,6 +30,7 @@ import com.android.internal.inputmethod.IInputMethodInfoListResultCallback; import com.android.internal.inputmethod.IInputMethodSubtypeResultCallback; import com.android.internal.inputmethod.IInputMethodSubtypeListResultCallback; import com.android.internal.inputmethod.IIntResultCallback; +import com.android.internal.inputmethod.IVoidResultCallback; /** * Public interface to the global input method manager, used by all client @@ -66,10 +67,11 @@ interface IInputMethodManager { in IInputBindResultResultCallback inputBindResult); void showInputMethodPickerFromClient(in IInputMethodClient client, - int auxiliarySubtypeMode); + int auxiliarySubtypeMode, in IVoidResultCallback resultCallback); void showInputMethodPickerFromSystem(in IInputMethodClient client, int auxiliarySubtypeMode, - int displayId); - void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); + int displayId, in IVoidResultCallback resultCallback); + void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId, + in IVoidResultCallback resultCallback); void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback); void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); @@ -78,7 +80,7 @@ interface IInputMethodManager { void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback); void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, - in float[] matrixValues); + in float[] matrixValues, in IVoidResultCallback resultCallback); oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */ diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 8d3faf4ce39e..b790056fb58e 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -68,7 +68,7 @@ private: }; static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl, - jlong width, jlong height, jboolean enableTripleBuffering) { + jlong width, jlong height, jint format, jboolean enableTripleBuffering) { String8 str8; if (jName) { const jchar* str16 = env->GetStringCritical(jName, nullptr); @@ -81,7 +81,7 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfac std::string name = str8.string(); sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width, - height, enableTripleBuffering); + height, format, enableTripleBuffering); queue->incStrong((void*)nativeCreate); return reinterpret_cast<jlong>(queue.get()); } @@ -104,9 +104,10 @@ static void nativeSetNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jlong queue->setNextTransaction(transaction); } -static void nativeUpdate(JNIEnv*env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, jlong height) { +static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, + jlong height, jint format) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); - queue->update(reinterpret_cast<SurfaceControl*>(surfaceControl), width, height); + queue->update(reinterpret_cast<SurfaceControl*>(surfaceControl), width, height, format); } static void nativeFlushShadowQueue(JNIEnv* env, jclass clazz, jlong ptr) { @@ -139,11 +140,11 @@ static void nativeSetTransactionCompleteCallback(JNIEnv* env, jclass clazz, jlon static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off - {"nativeCreate", "(Ljava/lang/String;JJJZ)J", (void*)nativeCreate}, + {"nativeCreate", "(Ljava/lang/String;JJJIZ)J", (void*)nativeCreate}, {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface}, {"nativeDestroy", "(J)V", (void*)nativeDestroy}, {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction}, - {"nativeUpdate", "(JJJJ)V", (void*)nativeUpdate}, + {"nativeUpdate", "(JJJJI)V", (void*)nativeUpdate}, {"nativeFlushShadowQueue", "(J)V", (void*)nativeFlushShadowQueue}, {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeSetTransactionCompleteCallback", diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b8e18072d6e5..7c670e1cb692 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1593,7 +1593,9 @@ static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { } static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj, - jobject toTokenObj, jobject focusedTokenObj, jint displayId) { + jobject toTokenObj, jstring windowNameJstr, + jobject focusedTokenObj, jstring focusedWindowNameJstr, + jint displayId) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); if (toTokenObj == NULL) return; @@ -1602,8 +1604,22 @@ static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionO if (focusedTokenObj != NULL) { focusedToken = ibinderForJavaObject(env, focusedTokenObj); } - transaction->setFocusedWindow(toToken, focusedToken, systemTime(SYSTEM_TIME_MONOTONIC), - displayId); + + FocusRequest request; + request.token = toToken; + if (windowNameJstr != NULL) { + ScopedUtfChars windowName(env, windowNameJstr); + request.windowName = windowName.c_str(); + } + + request.focusedToken = focusedToken; + if (focusedWindowNameJstr != NULL) { + ScopedUtfChars focusedWindowName(env, focusedWindowNameJstr); + request.focusedWindowName = focusedWindowName.c_str(); + } + request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + request.displayId = displayId; + transaction->setFocusedWindow(request); } static void nativeSetFrameTimelineVsync(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -1865,7 +1881,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetHandle }, {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint}, - {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Landroid/os/IBinder;I)V", + {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Ljava/lang/String;Landroid/os/IBinder;Ljava/lang/String;I)V", (void*)nativeSetFocusedWindow}, {"nativeSetFrameTimelineVsync", "(JJ)V", (void*)nativeSetFrameTimelineVsync }, diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto index 14b5c52acce4..900235ea65fb 100644 --- a/core/proto/android/server/biometrics.proto +++ b/core/proto/android/server/biometrics.proto @@ -120,8 +120,8 @@ message SensorStateProto { optional Modality modality = 2; - // State of the sensor's scheduler. True if currently handling an operation, false if idle. - optional bool is_busy = 3; + // State of the sensor's scheduler. + optional BiometricSchedulerProto scheduler = 3; // User states for this sensor. repeated UserStateProto user_states = 4; @@ -136,4 +136,39 @@ message UserStateProto { // Number of fingerprints enrolled optional int32 num_enrolled = 2; +} + +// BiometricScheduler dump +message BiometricSchedulerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Operation currently being handled by the BiometricScheduler + optional ClientMonitorEnum current_operation = 1; + + // Total number of operations that have been handled, not including the current one if one + // exists. Kept in FIFO order (most recent at the end of the array) + optional int32 total_operations = 2; + + // A list of recent past operations in the order which they were handled + repeated ClientMonitorEnum recent_operations = 3; +} + +// BaseClientMonitor subtypes +enum ClientMonitorEnum { + CM_NONE = 0; + CM_UPDATE_ACTIVE_USER = 1; + CM_ENROLL = 2; + CM_AUTHENTICATE = 3; + CM_REMOVE = 4; + CM_GET_AUTHENTICATOR_ID = 5; + CM_ENUMERATE = 6; + CM_INTERNAL_CLEANUP = 7; + CM_SET_FEATURE = 8; + CM_GET_FEATURE = 9; + CM_GENERATE_CHALLENGE = 10; + CM_REVOKE_CHALLENGE = 11; + CM_RESET_LOCKOUT = 12; + CM_DETECT_INTERACTION = 13; + CM_INVALIDATION_REQUESTER = 14; + CM_INVALIDATE = 15; }
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e1cd81c5a18e..f543373a35a1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -321,6 +321,9 @@ <protected-broadcast android:name="android.net.nsd.STATE_CHANGED" /> + <!-- For OMAPI --> + <protected-broadcast android:name="android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED" /> + <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" /> <protected-broadcast android:name="android.nfc.action.ALWAYS_ON_STATE_CHANGED" /> <protected-broadcast android:name="android.nfc.action.PREFERRED_PAYMENT_CHANGED" /> @@ -686,6 +689,10 @@ <!-- Made protected in S (was added in R) --> <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" /> + <!-- Added in S --> + <protected-broadcast android:name="android.app.action.MANAGED_PROFILE_CREATED" /> + <protected-broadcast android:name="android.app.action.PROVISIONED_MANAGED_DEVICE" /> + <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> @@ -1695,7 +1702,7 @@ <permission android:name="android.permission.MANAGE_IPSEC_TUNNELS" android:protectionLevel="signature|appop" /> - <!-- @hide Allows apps to create and manage Test Networks. + <!-- @SystemApi @hide Allows apps to create and manage Test Networks. <p>Granted only to shell. CTS tests will use UiAutomation.AdoptShellPermissionIdentity() to gain access. --> @@ -3109,6 +3116,12 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to do certain operations needed for + resume on reboot feature. + @hide --> + <permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to read system update info. @hide --> <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" @@ -4336,7 +4349,7 @@ <p>Not for use by third-party applications. --> <permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" - android:protectionLevel="signature|recents" /> + android:protectionLevel="signature|privileged|recents" /> <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> <!-- Allows an application to broadcast a notification that an application @@ -5387,6 +5400,9 @@ on-device data --> <attribution android:tag="OfflineLocationTimeZoneProviderService" android:label="@string/offline_location_time_zone_detection_service_attribution"/> + <!-- Attribution for Gnss Time Update service. --> + <attribution android:tag="GnssTimeUpdateService" + android:label="@string/gnss_time_update_service"/> <application android:process="system" android:persistent="true" diff --git a/packages/SystemUI/res/drawable/ic_camera_blocked.xml b/core/res/res/drawable/ic_camera_blocked.xml index 0161bcbd1937..0161bcbd1937 100644 --- a/packages/SystemUI/res/drawable/ic_camera_blocked.xml +++ b/core/res/res/drawable/ic_camera_blocked.xml diff --git a/packages/SystemUI/res/drawable/ic_mic_blocked.xml b/core/res/res/drawable/ic_mic_blocked.xml index 0ce7a581a7e5..0ce7a581a7e5 100644 --- a/packages/SystemUI/res/drawable/ic_mic_blocked.xml +++ b/core/res/res/drawable/ic_mic_blocked.xml diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 54e9dfd06fcb..0c2cfbf95080 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Nur WLAN"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM-übergreifende Anrufe"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> nach <xliff:g id="TIME_DELAY">{2}</xliff:g> Sekunden."</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 7c7c1786f71c..52baecf4d3d1 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string> <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string> <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 0cbbb018a082..c99fc18c5f99 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string> <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string> <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index e923d6560801..d12f810c2f8c 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string> <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string> <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 6e172426351b..f78119134c37 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string> <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string> <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 759fe0109685..4a87b1e0c2e4 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Hide Outgoing Caller ID"</string> <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string> <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 64bc3d118c69..bf4c0f678784 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Identificador de llamadas entrantes"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Ocultar identificador de llamadas salientes"</string> <string name="ColpMmi" msgid="4736462893284419302">"ID de línea conectada"</string> <string name="ColrMmi" msgid="5889782479745764278">"Restricción de ID de línea conectada"</string> <string name="CfMmi" msgid="8390012691099787178">"Desvío de llamadas"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index a591d319e5ff..fb7511ac65ab 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ફક્ત વાઇ-ફાઇ"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ક્રૉસ સિમ કૉલિંગ"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ફોરવર્ડ કર્યો નથી"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="TIME_DELAY">{2}</xliff:g> સેકન્ડ પછી <xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 2bdabc81db59..8cff2503c9b4 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -151,8 +151,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi בלבד"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"העברת שיחות בין כרטיסי SIM של <xliff:g id="SPN">%s</xliff:g>"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ללא העברה"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> כעבור <xliff:g id="TIME_DELAY">{2}</xliff:g> שניות"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 9fc9f369a258..b1583065a313 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ವೈ-ಫೈ ಮಾತ್ರ"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ಕ್ರಾಸ್-ಸಿಮ್ ಕರೆ ಮಾಡುವಿಕೆ"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index dc5175b3fb5a..37b2c114c5cc 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवळ वाय-फाय"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> क्रॉस सिम कॉलिंग"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित केला नाही"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंदांनंतर <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index a14ea625fb51..686bbe65bbd7 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi sahaja"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Panggilan Silang Sim"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 4ada7a2774ab..3b6960902296 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -149,7 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Alleen wifi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Cross-sim-bellen van <xliff:g id="SPN">%s</xliff:g>"</string> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Bellen met meerdere simkaarten van <xliff:g id="SPN">%s</xliff:g>"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> seconden"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 54e064dedd52..4b55e2de08a9 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"କେବଳ ୱାଇ-ଫାଇ"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> କ୍ରସ୍ SIM କଲିଂ"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ଫରୱାର୍ଡ କରାଯାଇନାହିଁ"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> ସେକେଣ୍ଡ ପରେ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 804b77d27b67..8fc507fec0cc 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ਕ੍ਰਾਸ-ਸਿਮ ਕਾਲਿੰਗ"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ਅੱਗੇ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> ਸਕਿੰਟਾਂ ਬਾਅਦ"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 9837b881e1a9..bdcf7af8fa96 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Identificador de chamadas recebidas"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Ocultar identificador de chamadas realizadas"</string> <string name="ColpMmi" msgid="4736462893284419302">"ID de linha conectada"</string> <string name="ColrMmi" msgid="5889782479745764278">"Restrição de ID de linha conectada"</string> <string name="CfMmi" msgid="8390012691099787178">"Encaminhamento de chamada"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index ef7ee9853083..e065e556381f 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"ID do Autor da Chamada"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Ocultar identificação do autor da chamada efetuada"</string> <string name="ColpMmi" msgid="4736462893284419302">"ID de linha ligada"</string> <string name="ColrMmi" msgid="5889782479745764278">"Restrição de ID de linha ligada"</string> <string name="CfMmi" msgid="8390012691099787178">"Encaminhamento de chamadas"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 9837b881e1a9..bdcf7af8fa96 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Identificador de chamadas recebidas"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Ocultar identificador de chamadas realizadas"</string> <string name="ColpMmi" msgid="4736462893284419302">"ID de linha conectada"</string> <string name="ColrMmi" msgid="5889782479745764278">"Restrição de ID de linha conectada"</string> <string name="CfMmi" msgid="8390012691099787178">"Encaminhamento de chamada"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index cfa9bf1605ae..a56a5f8e7d0c 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"පැමිණෙන අමතන්නාගේ ID"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"යන ඇමතුම්කරු ID සඟවන්න"</string> <string name="ColpMmi" msgid="4736462893284419302">"සම්බන්ධ කළ Line ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"සම්බන්ධ කළ Line ID සීමා කිරීම්"</string> <string name="CfMmi" msgid="8390012691099787178">"ඇමතුම ඉදිරියට යැවීම"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 5ee903183f40..8feea9cc738f 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vetëm Wi-Fi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Telefonatat e kryqëzuara të kartës SIM nga <xliff:g id="SPN">%s</xliff:g>"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nuk u transferua"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pas <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondash"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 2687aa10bf8a..29d90bdc99d4 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -236,9 +236,9 @@ <string name="shutdown_confirm" product="default" msgid="136816458966692315">"మీ ఫోన్ షట్డౌన్ చేయబడుతుంది."</string> <string name="shutdown_confirm_question" msgid="796151167261608447">"మీరు షట్ డౌన్ చేయాలనుకుంటున్నారా?"</string> <string name="reboot_safemode_title" msgid="5853949122655346734">"సురక్షిత మోడ్కు రీబూట్ చేయండి"</string> - <string name="reboot_safemode_confirm" msgid="1658357874737219624">"మీరు సురక్షిత మోడ్లోకి రీబూట్ చేయాలనుకుంటున్నారా? దీని వలన మీరు ఇన్స్టాల్ చేసిన అన్ని మూడవ పక్షం అనువర్తనాలు నిలిపివేయబడతాయి. ఇవి మీరు మళ్లీ రీబూట్ చేసినప్పుడు పునరుద్ధరించబడతాయి."</string> + <string name="reboot_safemode_confirm" msgid="1658357874737219624">"మీరు సురక్షిత మోడ్లోకి రీబూట్ చేయాలనుకుంటున్నారా? దీని వలన మీరు ఇన్స్టాల్ చేసిన అన్ని మూడవ పక్షం యాప్లు నిలిపివేయబడతాయి. ఇవి మీరు మళ్లీ రీబూట్ చేసినప్పుడు పునరుద్ధరించబడతాయి."</string> <string name="recent_tasks_title" msgid="8183172372995396653">"ఇటీవలివి"</string> - <string name="no_recent_tasks" msgid="9063946524312275906">"ఇటీవలి అనువర్తనాలు ఏవీ లేవు."</string> + <string name="no_recent_tasks" msgid="9063946524312275906">"ఇటీవలి యాప్లు ఏవీ లేవు."</string> <string name="global_actions" product="tablet" msgid="4412132498517933867">"టాబ్లెట్ ఎంపికలు"</string> <string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV ఎంపికలు"</string> <string name="global_actions" product="default" msgid="6410072189971495460">"ఫోన్ ఎంపికలు"</string> @@ -344,13 +344,13 @@ <string name="permlab_expandStatusBar" msgid="1184232794782141698">"స్థితి పట్టీని విస్తరింపజేయడం/కుదించడం"</string> <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"స్థితి బార్ను విస్తరింపజేయడానికి లేదా కుదించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_install_shortcut" msgid="7451554307502256221">"షార్ట్కట్లను ఇన్స్టాల్ చేయడం"</string> - <string name="permdesc_install_shortcut" msgid="4476328467240212503">"వినియోగదారు ప్రమేయం లేకుండానే హోమ్స్క్రీన్ సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_install_shortcut" msgid="4476328467240212503">"వినియోగదారు ప్రమేయం లేకుండానే హోమ్స్క్రీన్ సత్వరమార్గాలను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"సత్వరమార్గాలను అన్ఇన్స్టాల్ చేయడం"</string> - <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"వినియోగదారు ప్రమేయం లేకుండానే హోమ్స్క్రీన్ సత్వరమార్గాలను తీసివేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"వినియోగదారు ప్రమేయం లేకుండానే హోమ్స్క్రీన్ సత్వరమార్గాలను తీసివేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"అవుట్గోయింగ్ కాల్లను దారి మళ్లించడం"</string> - <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"కాల్ను వేరే నంబర్కు దారి మళ్లించే లేదా మొత్తంగా కాల్ను ఆపివేసే ఎంపిక సహాయంతో అవుట్గోయింగ్ కాల్ సమయంలో డయల్ చేయబడుతున్న నంబర్ను చూడటానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"కాల్ను వేరే నంబర్కు దారి మళ్లించే లేదా మొత్తంగా కాల్ను ఆపివేసే ఎంపిక సహాయంతో అవుట్గోయింగ్ కాల్ సమయంలో డయల్ చేయబడుతున్న నంబర్ను చూడటానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ఫోన్ కాల్లకు సమాధానమివ్వు"</string> - <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ఇన్కమింగ్ ఫోన్ కాల్లకు సమాధానమివ్వడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ఇన్కమింగ్ ఫోన్ కాల్లకు సమాధానమివ్వడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_receiveSms" msgid="505961632050451881">"వచన సందేశాలను (SMS) స్వీకరించడం"</string> <string name="permdesc_receiveSms" msgid="1797345626687832285">"SMS సందేశాలను స్వీకరించడానికి మరియు ప్రాసెస్ చేయడానికి యాప్ను అనుమతిస్తుంది. యాప్ మీ డివైజ్కు పంపబడిన సందేశాలను మీకు చూపకుండానే పర్యవేక్షించగలదని లేదా తొలగించగలదని దీని అర్థం."</string> <string name="permlab_receiveMms" msgid="4000650116674380275">"వచన సందేశాలను (MMS) స్వీకరించడం"</string> @@ -396,7 +396,7 @@ <string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ నిల్వ స్థలాన్ని అంచనా వేయడం"</string> <string name="permdesc_getPackageSize" msgid="742743530909966782">"యాప్ కోడ్, డేటా మరియు కాష్ పరిమాణాలను తిరిగి పొందడానికి దాన్ని అనుమతిస్తుంది"</string> <string name="permlab_writeSettings" msgid="8057285063719277394">"సిస్టమ్ సెట్టింగ్లను మార్చడం"</string> - <string name="permdesc_writeSettings" msgid="8293047411196067188">"సిస్టమ్ యొక్క సెట్టింగ్ల డేటాను సవరించడానికి అనువర్తనాన్ని అనుమతిస్తుంది. హానికరమైన యాప్లు మీ సిస్టమ్ యొక్క కాన్ఫిగరేషన్ను నాశనం చేయవచ్చు."</string> + <string name="permdesc_writeSettings" msgid="8293047411196067188">"సిస్టమ్ యొక్క సెట్టింగ్ల డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ సిస్టమ్ యొక్క కాన్ఫిగరేషన్ను నాశనం చేయవచ్చు."</string> <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"ప్రారంభంలో అమలు చేయడం"</string> <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"సిస్టమ్ బూటింగ్ను పూర్తి చేసిన వెంటనే దానికదే ప్రారంభించబడటానికి యాప్ను అనుమతిస్తుంది. ఇది టాబ్లెట్ను ప్రారంభించడానికి ఎక్కువ సమయం పట్టేలా చేయవచ్చు మరియు ఎల్లప్పుడూ అమలు చేయడం ద్వారా మొత్తం టాబ్లెట్ను నెమ్మదిగా పని చేయడానికి యాప్ను అనుమతించేలా చేయవచ్చు."</string> <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"సిస్టమ్ బూటింగ్ను పూర్తి చేసిన వెంటనే యాప్ దానికదే ప్రారంభం కావడానికి అనుమతిస్తుంది. ఇది మీ Android TV పరికరం ప్రారంభం కావడానికి ఎక్కువ సమయం పట్టేలా చేయవచ్చు మరియు ఎల్లప్పుడూ అమలు కావడం ద్వారా మొత్తం పరికరం పనితీరును నెమ్మది చేయడానికి యాప్ను అనుమతించవచ్చు."</string> @@ -444,7 +444,7 @@ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"బ్యాక్గ్రౌండ్లో ఆడియోను రికార్డ్ చేయగలదు"</string> <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"మైక్రోఫోన్ను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string> <string name="permlab_sim_communication" msgid="176788115994050692">"SIMకి ఆదేశాలను పంపడం"</string> - <string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్కు ఆదేశాలను పంపడానికి అనువర్తనాన్ని అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string> + <string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్కు ఆదేశాలను పంపడానికి యాప్ను అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string> <string name="permlab_activityRecognition" msgid="1782303296053990884">"భౌతిక కార్యాకలాపాన్ని గుర్తించండి"</string> <string name="permdesc_activityRecognition" msgid="8667484762991357519">"ఈ యాప్ మీ భౌతిక కార్యాకలాపాన్ని గుర్తించగలదు."</string> <string name="permlab_camera" msgid="6320282492904119413">"చిత్రాలు మరియు వీడియోలు తీయడం"</string> @@ -461,11 +461,11 @@ <string name="permlab_callPhone" msgid="1798582257194643320">"నేరుగా కాల్ చేసే ఫోన్ నంబర్లు"</string> <string name="permdesc_callPhone" msgid="5439809516131609109">"మీ ప్రమేయం లేకుండా ఫోన్ నంబర్లకు కాల్ చేయడానికి యాప్ను అనుమతిస్తుంది. దీని వలన అనుకోని ఛార్జీలు విధించబడవచ్చు లేదా కాల్లు రావచ్చు. ఇది అత్యవసర నంబర్లకు కాల్ చేయడానికి యాప్ను అనుమతించదని గుర్తుంచుకోండి. హానికరమైన యాప్లు మీ నిర్ధారణ లేకుండానే కాల్లు చేయడం ద్వారా మీకు డబ్బు ఖర్చయ్యేలా చేయవచ్చు."</string> <string name="permlab_accessImsCallService" msgid="442192920714863782">"IMS కాల్ సేవ యాక్సెస్ అనుమతి"</string> - <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"మీ ప్రమేయం లేకుండా కాల్లు చేయడం కోసం IMS సేవను ఉపయోగించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"మీ ప్రమేయం లేకుండా కాల్లు చేయడం కోసం IMS సేవను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_readPhoneState" msgid="8138526903259297969">"ఫోన్ స్థితి మరియు గుర్తింపుని చదవడం"</string> <string name="permdesc_readPhoneState" msgid="7229063553502788058">"పరికరం యొక్క ఫోన్ ఫీచర్లను యాక్సెస్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి ఫోన్ నంబర్ మరియు పరికరం IDలను, కాల్ సక్రియంగా ఉందా లేదా అనే విషయాన్ని మరియు కాల్ ద్వారా కనెక్ట్ చేయబడిన రిమోట్ నంబర్ను కనుగొనడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"కాల్లను సిస్టమ్ ద్వారా వెళ్లేలా చేయి"</string> - <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"కాలింగ్ అనుభవాన్ని మెరుగుపరచడం కోసం తన కాల్లను సిస్టమ్ ద్వారా వెళ్లేలా చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"కాలింగ్ అనుభవాన్ని మెరుగుపరచడం కోసం తన కాల్లను సిస్టమ్ ద్వారా వెళ్లేలా చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_callCompanionApp" msgid="3654373653014126884">"సిస్టమ్ ద్వారా కాల్లను చూసి, నియంత్రించండి."</string> <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"పరికరంలో కొనసాగుతున్న కాల్లను చూడడానికి మరియు నియంత్రించడానికి యాప్ను అనుమతిస్తుంది. ఇందులో కాల్ కోసం కాల్ల నంబర్లు మరియు రాష్ట్ర కాల్ వంటి సమాచారం ఉంటుంది."</string> <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"ఆడియో రికార్డ్ పరిమితుల నుండి మినహాయింపు"</string> @@ -483,9 +483,9 @@ <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"మీ Android TV పరికరం స్లీప్ మోడ్లోకి వెళ్లకుండా నివారించడానికి యాప్ని అనుమతిస్తుంది."</string> <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"నిద్రావస్థకి వెళ్లకుండా ఫోన్ను నిరోధించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_transmitIr" msgid="8077196086358004010">"ఇన్ఫ్రారెడ్ ప్రసరణ"</string> - <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"టాబ్లెట్ యొక్క ఇన్ఫ్రారెడ్ ట్రాన్స్మిటర్ను ఉపయోగించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"టాబ్లెట్ యొక్క ఇన్ఫ్రారెడ్ ట్రాన్స్మిటర్ను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"మీ Android TV పరికరం యొక్క ఇన్ఫ్రారెడ్ ట్రాన్స్మిటర్ని ఉపయోగించడానికి యాప్ని అనుమతిస్తుంది."</string> - <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"ఫోన్ యొక్క ఇన్ఫ్రారెడ్ ట్రాన్స్మిటర్ను ఉపయోగించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"ఫోన్ యొక్క ఇన్ఫ్రారెడ్ ట్రాన్స్మిటర్ను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_setWallpaper" msgid="6959514622698794511">"వాల్పేపర్ను సెట్ చేయడం"</string> <string name="permdesc_setWallpaper" msgid="2973996714129021397">"సిస్టమ్ వాల్పేపర్ను సెట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"మీ వాల్పేపర్ పరిమాణాన్ని సర్దుబాటు చేయడం"</string> @@ -507,13 +507,13 @@ <string name="permlab_changeTetherState" msgid="9079611809931863861">"టీథర్ చేయబడిన కనెక్టివిటీని మార్చడం"</string> <string name="permdesc_changeTetherState" msgid="3025129606422533085">"టీథర్ చేసిన నెట్వర్క్ కనెక్టివిటీ యొక్క స్థితిని మార్చడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_accessWifiState" msgid="5552488500317911052">"Wi-Fi కనెక్షన్లను వీక్షించడం"</string> - <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Wi-Fi ప్రారంభించబడిందా, లేదా మరియు కనెక్ట్ చేయబడిన Wi-Fi పరికరాల పేరు వంటి Wi-Fi నెట్వర్కింగ్ గురించి సమాచారాన్ని వీక్షించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Wi-Fi ప్రారంభించబడిందా, లేదా మరియు కనెక్ట్ చేయబడిన Wi-Fi పరికరాల పేరు వంటి Wi-Fi నెట్వర్కింగ్ గురించి సమాచారాన్ని వీక్షించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_changeWifiState" msgid="7947824109713181554">"Wi-Fiకి కనెక్ట్ చేయడం మరియు దాని నుండి డిస్కనెక్ట్ చేయడం"</string> <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Wi-Fi యాక్సెస్ స్థానాలకు కనెక్ట్ చేయడానికి మరియు వాటి నుండి డిస్కనెక్ట్ చేయడానికి మరియు Wi-Fi నెట్వర్క్ల కోసం పరికర కాన్ఫిగరేషన్కు మార్పులు చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"Wi-Fi Multicast స్వీకరణను అనుమతించడం"</string> <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"మల్టీక్యాస్ట్ చిరునామాలను ఉపయోగించి మీ టాబ్లెట్కు మాత్రమే కాకుండా Wi-Fi నెట్వర్క్లోని అన్ని పరికరాలకు పంపబడిన ప్యాకెట్లను స్వీకరించడానికి యాప్ను అనుమతిస్తుంది. మల్టీక్యాస్ట్ యేతర మోడ్ కంటే ఇది ఎక్కువ పవర్ ఉపయోగిస్తుంది."</string> <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"మల్టీక్యాస్ట్ చిరునామాలను ఉపయోగించి మీ Android TV పరికరానికి మాత్రమే కాకుండా Wi-Fi నెట్వర్క్లోని అన్ని పరికరాలకు పంపిన ప్యాకెట్లను స్వీకరించడానికి యాప్ని అనుమతిస్తుంది. ఇది మల్టీక్యాస్ట్ యేతర మోడ్ కంటే ఎక్కువ పవర్ను ఉపయోగిస్తుంది."</string> - <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"మల్టీక్యాస్ట్ చిరునామాలను ఉపయోగించి మీ ఫోన్కు మాత్రమే కాకుండా Wi-Fi నెట్వర్క్లోని అన్ని పరికరాలకు పంపబడిన ప్యాకెట్లను స్వీకరించడానికి అనువర్తనాన్ని అనుమతిస్తుంది. మల్టీక్యాస్ట్ యేతర మోడ్ కంటే ఇది ఎక్కువ పవర్ ఉపయోగిస్తుంది."</string> + <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"మల్టీక్యాస్ట్ చిరునామాలను ఉపయోగించి మీ ఫోన్కు మాత్రమే కాకుండా Wi-Fi నెట్వర్క్లోని అన్ని పరికరాలకు పంపబడిన ప్యాకెట్లను స్వీకరించడానికి యాప్ను అనుమతిస్తుంది. మల్టీక్యాస్ట్ యేతర మోడ్ కంటే ఇది ఎక్కువ పవర్ ఉపయోగిస్తుంది."</string> <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"బ్లూటూత్ సెట్టింగ్లను యాక్సెస్ చేయడం"</string> <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"స్థానిక బ్లూటూత్ టాబ్లెట్ను కాన్ఫిగర్ చేయడానికి మరియు రిమోట్ పరికరాలతో దాన్ని కనుగొనడానికి మరియు జత చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"మీ Android TV పరికరంలో బ్లూటూత్ను కాన్ఫిగర్ చేయడానికి మరియు రిమోట్ పరికరాలతో దాన్ని కనుగొని, జత చేయడానికి యాప్ను అనుమతిస్తుంది."</string> @@ -521,7 +521,7 @@ <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXకు కనెక్ట్ చేయడం మరియు దాని నుండి డిస్కనెక్ట్ చేయడం"</string> <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Wi-Fi ప్రారంభించబడిందా, లేదా మరియు కనెక్ట్ చేయబడిన WiMAX నెట్వర్క్ల గురించి సమాచారాన్ని కనుగొనడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_changeWimaxState" msgid="6223305780806267462">"WiMAX స్థితిని మార్చడం"</string> - <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"WiMAX నెట్వర్క్లకు టాబ్లెట్ను కనెక్ట్ చేయడానికి మరియు వాటి నుండి టాబ్లెట్ను డిస్కనెక్ట్ చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"WiMAX నెట్వర్క్లకు టాబ్లెట్ను కనెక్ట్ చేయడానికి మరియు వాటి నుండి టాబ్లెట్ను డిస్కనెక్ట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"మీ Android TV పరికరాన్ని WiMAX నెట్వర్క్లకు కనెక్ట్ చేయడానికి లేదా డిస్కనెక్ట్ చేయడానికి యాప్ని అనుమతిస్తుంది."</string> <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"WiMAX నెట్వర్క్లకు ఫోన్ను కనెక్ట్ చేయడానికి మరియు వాటి నుండి ఫోన్ను డిస్కనెక్ట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bluetooth" msgid="586333280736937209">"బ్లూటూత్ పరికరాలతో జత చేయడం"</string> @@ -637,19 +637,19 @@ <string name="permlab_sdcardWrite" msgid="4863021819671416668">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్లను సవరించండి లేదా తొలగించండి"</string> <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్లను రాయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_use_sip" msgid="8250774565189337477">"SIP కాల్లను చేయడానికి/స్వీకరించడానికి"</string> - <string name="permdesc_use_sip" msgid="3590270893253204451">"SIP కాల్లను చేయడానికి మరియు స్వీకరించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_use_sip" msgid="3590270893253204451">"SIP కాల్లను చేయడానికి మరియు స్వీకరించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"కొత్త టెలికామ్ SIM కనెక్షన్లను నమోదు చేయడం"</string> - <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"కొత్త టెలికామ్ SIM కనెక్షన్లను నమోదు చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"కొత్త టెలికామ్ SIM కనెక్షన్లను నమోదు చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_register_call_provider" msgid="6135073566140050702">"కొత్త టెలికామ్ కనెక్షన్లను నమోదు చేయడం"</string> - <string name="permdesc_register_call_provider" msgid="4201429251459068613">"కొత్త టెలికామ్ కనెక్షన్లను నమోదు చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_register_call_provider" msgid="4201429251459068613">"కొత్త టెలికామ్ కనెక్షన్లను నమోదు చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_connection_manager" msgid="3179365584691166915">"టెలికామ్ కనెక్షన్లను నిర్వహించడం"</string> - <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్లను నిర్వహించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్లను నిర్వహించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bind_incall_service" msgid="5990625112603493016">"ఇన్-కాల్ స్క్రీన్తో పరస్పర చర్య చేయడం"</string> - <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"వినియోగదారునికి ఇన్-కాల్ స్క్రీన్ ఎప్పుడు, ఎలా కనిపించాలనే దాన్ని నియంత్రించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"వినియోగదారునికి ఇన్-కాల్ స్క్రీన్ ఎప్పుడు, ఎలా కనిపించాలనే దాన్ని నియంత్రించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bind_connection_service" msgid="5409268245525024736">"టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడం"</string> - <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"కాల్లు చేయడం/స్వీకరించడం కోసం టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"కాల్లు చేయడం/స్వీకరించడం కోసం టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_control_incall_experience" msgid="6436863486094352987">"ఇన్-కాల్ వినియోగదారు అనుభవాన్ని అందించడం"</string> - <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"ఇన్-కాల్ వినియోగదారుని అనుభవాన్ని అందించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"ఇన్-కాల్ వినియోగదారుని అనుభవాన్ని అందించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"చారిత్రక నెట్వర్క్ వినియోగాన్ని చదవడం"</string> <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"నిర్దిష్ట నెట్వర్క్లు మరియు యాప్ల కోసం చారిత్రాత్మక నెట్వర్క్ వినియోగాన్ని చదవడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"నెట్వర్క్ విధానాన్ని నిర్వహించడం"</string> @@ -657,31 +657,31 @@ <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"నెట్వర్క్ వినియోగ అకౌంటింగ్ను సవరించడం"</string> <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్లలో నెట్వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని సవరించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string> <string name="permlab_accessNotifications" msgid="7130360248191984741">"నోటిఫికేషన్లను యాక్సెస్ చేయడం"</string> - <string name="permdesc_accessNotifications" msgid="761730149268789668">"నోటిఫికేషన్లను, ఇతర అనువర్తనాల ద్వారా పోస్ట్ చేయబడిన వాటిని తిరిగి పొందడానికి, పరిశీలించడానికి మరియు క్లియర్ చేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_accessNotifications" msgid="761730149268789668">"నోటిఫికేషన్లను, ఇతర అనువర్తనాల ద్వారా పోస్ట్ చేయబడిన వాటిని తిరిగి పొందడానికి, పరిశీలించడానికి మరియు క్లియర్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"నోటిఫికేషన్ పరిశీలన సేవకు అనుబంధించడం"</string> <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"నోటిఫికేషన్ పరిశీలన సేవ యొక్క అగ్ర-స్థాయి ఇంటర్ఫేస్కు అనుబంధించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాల కోసం ఎప్పటికీ అవసరం ఉండకూడదు."</string> <string name="permlab_bindConditionProviderService" msgid="5245421224814878483">"షరతు ప్రదాత సేవకు అనుబంధించడం"</string> <string name="permdesc_bindConditionProviderService" msgid="6106018791256120258">"షరతు ప్రదాత సేవ యొక్క అగ్ర-స్థాయి ఇంటర్ఫేస్కు అనుబంధించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_bindDreamService" msgid="4776175992848982706">"డ్రీమ్ సేవకి అనుబంధించడం"</string> <string name="permdesc_bindDreamService" msgid="9129615743300572973">"డ్రీమ్ సేవ యొక్క అగ్ర-స్థాయి ఇంటర్ఫేస్కు అనుబంధించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> - <string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"క్యారియర్ అందించిన కాన్ఫిగరేషన్ అనువర్తనాన్ని అభ్యర్థించడం"</string> - <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"క్యారియర్ అందించిన కాన్ఫిగరేషన్ అనువర్తనాన్ని అభ్యర్థించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాల కోసం ఎప్పటికీ అవసరం ఉండకూడదు."</string> + <string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"క్యారియర్ అందించిన కాన్ఫిగరేషన్ యాప్ను అభ్యర్థించడం"</string> + <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"క్యారియర్ అందించిన కాన్ఫిగరేషన్ యాప్ను అభ్యర్థించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాల కోసం ఎప్పటికీ అవసరం ఉండకూడదు."</string> <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"నెట్వర్క్ పరిస్థితులపై పరిశీలనల గురించి తెలుసుకోవడం"</string> - <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"నెట్వర్క్ పరిస్థితులపై పరిశీలనల గురించి తెలుసుకోవడానికి అనువర్తనాన్ని అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండకూడదు."</string> + <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"నెట్వర్క్ పరిస్థితులపై పరిశీలనల గురించి తెలుసుకోవడానికి యాప్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండకూడదు."</string> <string name="permlab_setInputCalibration" msgid="932069700285223434">"ఇన్పుట్ పరికరం క్రమాంకనాన్ని మార్చండి"</string> - <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"టచ్ స్క్రీన్ యొక్క క్రమాంకన పరామితులను సవరించడానికి అనువర్తనాన్ని అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> + <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"టచ్ స్క్రీన్ యొక్క క్రమాంకన పరామితులను సవరించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"DRM ప్రమాణపత్రాలను యాక్సెస్ చేయడం"</string> - <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"DRM ప్రమాణపత్రాలను కేటాయించడానికి మరియు ఉపయోగించడానికి అనువర్తనాన్ని అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> + <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"DRM ప్రమాణపత్రాలను కేటాయించడానికి మరియు ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_handoverStatus" msgid="7620438488137057281">"Android Beam బదిలీ స్థితిని స్వీకరించడం"</string> - <string name="permdesc_handoverStatus" msgid="3842269451732571070">"ప్రస్తుత Android Beam బదిలీలకు సంబంధించిన సమాచారాన్ని స్వీకరించడానికి ఈ అనువర్తనాన్ని అనుమతిస్తుంది"</string> + <string name="permdesc_handoverStatus" msgid="3842269451732571070">"ప్రస్తుత Android Beam బదిలీలకు సంబంధించిన సమాచారాన్ని స్వీకరించడానికి ఈ యాప్ను అనుమతిస్తుంది"</string> <string name="permlab_removeDrmCertificates" msgid="710576248717404416">"DRM ప్రమాణపత్రాలను తీసివేయడం"</string> - <string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"DRM ప్రమాణపత్రాలను తీసివేయడానికి అనువర్తనాన్ని అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> + <string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"DRM ప్రమాణపత్రాలను తీసివేయడానికి యాప్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"క్యారియర్ సందేశ సేవకు అనుబంధించడం"</string> <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"క్యారియర్ సందేశ సేవ యొక్క అగ్ర-స్థాయి ఇంటర్ఫేస్కు అనుబంధించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"క్యారియర్ సేవలకు అనుబంధించడం"</string> <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"క్యారియర్ సేవలకు అనుబంధించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ అనువర్తనాలకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_access_notification_policy" msgid="5524112842876975537">"అంతరాయం కలిగించవద్దును యాక్సెస్ చేయడం"</string> - <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు వ్రాయడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు వ్రాయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"వీక్షణ అనుమతి వినియోగాన్ని ప్రారంభించండి"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"యాప్నకు అనుమతి వినియోగాన్ని ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ ఇటువంటి అనుమతి అవసరం ఉండదు."</string> <string name="policylab_limitPassword" msgid="4851829918814422199">"పాస్వర్డ్ నియమాలను సెట్ చేయండి"</string> @@ -1126,7 +1126,7 @@ <string name="low_internal_storage_view_text" msgid="8172166728369697835">"కొన్ని సిస్టమ్ కార్యాచరణలు పని చేయకపోవచ్చు"</string> <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"సిస్టమ్ కోసం తగినంత నిల్వ లేదు. మీకు 250MB ఖాళీ స్థలం ఉందని నిర్ధారించుకుని, పునఃప్రారంభించండి."</string> <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> అమలులో ఉంది"</string> - <string name="app_running_notification_text" msgid="5120815883400228566">"మరింత సమాచారం కోసం లేదా అనువర్తనాన్ని ఆపివేయడం కోసం నొక్కండి."</string> + <string name="app_running_notification_text" msgid="5120815883400228566">"మరింత సమాచారం కోసం లేదా యాప్ను ఆపివేయడం కోసం నొక్కండి."</string> <string name="ok" msgid="2646370155170753815">"సరే"</string> <string name="cancel" msgid="6908697720451760115">"రద్దు చేయి"</string> <string name="yes" msgid="9069828999585032361">"సరే"</string> @@ -1159,28 +1159,28 @@ <string name="whichSendToApplication" msgid="77101541959464018">"దీన్ని ఉపయోగించి పంపండి"</string> <string name="whichSendToApplicationNamed" msgid="3385686512014670003">"%1$sని ఉపయోగించి పంపండి"</string> <string name="whichSendToApplicationLabel" msgid="3543240188816513303">"పంపు"</string> - <string name="whichHomeApplication" msgid="8276350727038396616">"హోమ్ అనువర్తనాన్ని ఎంచుకోండి"</string> + <string name="whichHomeApplication" msgid="8276350727038396616">"హోమ్ యాప్ను ఎంచుకోండి"</string> <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"%1$sని హోమ్గా ఉపయోగించండి"</string> <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"చిత్రాన్ని క్యాప్చర్ చేయి"</string> <string name="whichImageCaptureApplication" msgid="2737413019463215284">"దీనితో చిత్రాన్ని క్యాప్చర్ చేయి"</string> <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"%1$sతో చిత్రాన్ని క్యాప్చర్ చేయండి"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"చిత్రాన్ని క్యాప్చర్ చేయి"</string> <string name="alwaysUse" msgid="3153558199076112903">"ఈ చర్యకు డిఫాల్ట్గా ఉపయోగించండి."</string> - <string name="use_a_different_app" msgid="4987790276170972776">"వేరొక అనువర్తనాన్ని ఉపయోగించండి"</string> - <string name="clearDefaultHintMsg" msgid="1325866337702524936">"సిస్టమ్ సెట్టింగ్లు > అనువర్తనాలు > డౌన్లోడ్ చేయబడినవిలో డిఫాల్ట్ను క్లియర్ చేయి."</string> + <string name="use_a_different_app" msgid="4987790276170972776">"వేరొక యాప్ను ఉపయోగించండి"</string> + <string name="clearDefaultHintMsg" msgid="1325866337702524936">"సిస్టమ్ సెట్టింగ్లు > యాప్లు > డౌన్లోడ్ చేయబడినవిలో డిఫాల్ట్ను క్లియర్ చేయి."</string> <string name="chooseActivity" msgid="8563390197659779956">"చర్యను ఎంచుకోండి"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"USB పరికరం కోసం యాప్ను ఎంచుకోండి"</string> - <string name="noApplications" msgid="1186909265235544019">"ఈ చర్యను అమలు చేయగల అనువర్తనాలు ఏవీ లేవు."</string> + <string name="noApplications" msgid="1186909265235544019">"ఈ చర్యను అమలు చేయగల యాప్లు ఏవీ లేవు."</string> <string name="aerr_application" msgid="4090916809370389109">"<xliff:g id="APPLICATION">%1$s</xliff:g> ఆపివేయబడింది"</string> <string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> ఆపివేయబడింది"</string> <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> పునరావృతంగా ఆపివేయబడుతోంది"</string> <string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> పునరావృతంగా ఆపివేయబడుతోంది"</string> - <string name="aerr_restart" msgid="2789618625210505419">"అనువర్తనాన్ని మళ్లీ తెరువు"</string> + <string name="aerr_restart" msgid="2789618625210505419">"యాప్ను మళ్లీ తెరువు"</string> <string name="aerr_report" msgid="3095644466849299308">"ఫీడ్బ్యాక్ను పంపు"</string> <string name="aerr_close" msgid="3398336821267021852">"మూసివేయి"</string> <string name="aerr_mute" msgid="2304972923480211376">"పరికరం పునఃప్రారంభమయ్యే వరకు మ్యూట్ చేయి"</string> <string name="aerr_wait" msgid="3198677780474548217">"వేచి ఉండండి"</string> - <string name="aerr_close_app" msgid="8318883106083050970">"అనువర్తనాన్ని మూసివేయి"</string> + <string name="aerr_close_app" msgid="8318883106083050970">"యాప్ను మూసివేయి"</string> <string name="anr_title" msgid="7290329487067300120"></string> <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> ప్రతిస్పందించడం లేదు"</string> <string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ప్రతిస్పందించడం లేదు"</string> @@ -1195,7 +1195,7 @@ <string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> వాస్తవంగా ప్రారంభించబడింది."</string> <string name="screen_compat_mode_scale" msgid="8627359598437527726">"ప్రమాణం"</string> <string name="screen_compat_mode_show" msgid="5080361367584709857">"ఎల్లప్పుడూ చూపు"</string> - <string name="screen_compat_mode_hint" msgid="4032272159093750908">"సిస్టమ్ సెట్టింగ్లు > అనువర్తనాలు > డౌన్లోడ్ చేసినవిలో దీన్ని పునఃప్రారంభించండి."</string> + <string name="screen_compat_mode_hint" msgid="4032272159093750908">"సిస్టమ్ సెట్టింగ్లు > యాప్లు > డౌన్లోడ్ చేసినవిలో దీన్ని పునఃప్రారంభించండి."</string> <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుత ప్రదర్శన పరిమాణ సెట్టింగ్కు మద్దతు ఇవ్వదు, దీని వలన ఊహించని సమస్యలు తలెత్తవచ్చు."</string> <string name="unsupported_display_size_show" msgid="980129850974919375">"ఎల్లప్పుడూ చూపు"</string> <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Android OS యొక్క అననుకూల వెర్షన్ కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రూపొందించబడింది మరియు ఊహించని సమస్యలు తలెత్తవచ్చు. యాప్ యొక్క అప్డేట్ చేసిన వెర్షన్ అందుబాటులో ఉండవచ్చు."</string> @@ -1340,7 +1340,7 @@ <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"బగ్ నివేదికను తీస్తోంది…"</string> <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"బగ్ నివేదికను భాగస్వామ్యం చేయాలా?"</string> <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"బగ్ నివేదికను భాగస్వామ్యం చేస్తోంది..."</string> - <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"మీ నిర్వాహకులు ఈ పరికరం సమస్యకు పరిష్కారాన్ని కనుగొనడంలో సహాయం కోసం బగ్ నివేదికను అభ్యర్థించారు. అనువర్తనాలు మరియు డేటా భాగస్వామ్యం చేయబడవచ్చు."</string> + <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"మీ నిర్వాహకులు ఈ పరికరం సమస్యకు పరిష్కారాన్ని కనుగొనడంలో సహాయం కోసం బగ్ నివేదికను అభ్యర్థించారు. యాప్లు మరియు డేటా భాగస్వామ్యం చేయబడవచ్చు."</string> <string name="share_remote_bugreport_action" msgid="7630880678785123682">"షేర్ చేయి"</string> <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"తిరస్కరిస్తున్నాను"</string> <string name="select_input_method" msgid="3971267998568587025">"ఇన్పుట్ పద్ధతిని ఎంచుకోండి"</string> @@ -1403,13 +1403,13 @@ <string name="ext_media_status_missing" msgid="6520746443048867314">"చొప్పించబడలేదు"</string> <string name="activity_list_empty" msgid="4219430010716034252">"సరిపోలే కార్యాచరణలు కనుగొనబడలేదు."</string> <string name="permlab_route_media_output" msgid="8048124531439513118">"మీడియా అవుట్పుట్ను మళ్లించడం"</string> - <string name="permdesc_route_media_output" msgid="1759683269387729675">"మీడియా అవుట్పుట్ను ఇతర బాహ్య పరికరాలకు మళ్లించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_route_media_output" msgid="1759683269387729675">"మీడియా అవుట్పుట్ను ఇతర బాహ్య పరికరాలకు మళ్లించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_readInstallSessions" msgid="7279049337895583621">"ఇన్స్టాల్ సెషన్లను చదవడం"</string> - <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"ఇన్స్టాల్ సెషన్లను చదవడానికి అనువర్తనాన్ని అనుమతిస్తుంది. ఇది సక్రియ ప్యాకేజీ ఇన్స్టాలేషన్ల గురించి వివరాలను చూడటానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"ఇన్స్టాల్ సెషన్లను చదవడానికి యాప్ను అనుమతిస్తుంది. ఇది సక్రియ ప్యాకేజీ ఇన్స్టాలేషన్ల గురించి వివరాలను చూడటానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_requestInstallPackages" msgid="7600020863445351154">"ఇన్స్టాల్ ప్యాకేజీలను అభ్యర్థించడం"</string> - <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"ప్యాకేజీల ఇన్స్టాలేషన్ అభ్యర్థించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"ప్యాకేజీల ఇన్స్టాలేషన్ అభ్యర్థించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"ప్యాకేజీలను తొలగించడానికి అభ్యర్థించు"</string> - <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"ప్యాకేజీల తొలగింపును అభ్యర్థించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> + <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"ప్యాకేజీల తొలగింపును అభ్యర్థించడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"బ్యాటరీ అనుకూలీకరణలను విస్మరించడానికి అడగాలి"</string> <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ఆ యాప్ కోసం బ్యాటరీ అనుకూలీకరణలు విస్మరించేలా అనుమతి కోరడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"జూమ్ నియంత్రణ కోసం రెండుసార్లు నొక్కండి"</string> @@ -1430,8 +1430,8 @@ <string name="deny" msgid="6632259981847676572">"తిరస్కరించండి"</string> <string name="permission_request_notification_title" msgid="1810025922441048273">"అనుమతి అభ్యర్థించబడింది"</string> <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"ఖాతా <xliff:g id="ACCOUNT">%s</xliff:g> కోసం\nఅనుమతి అభ్యర్థించబడింది."</string> - <string name="forward_intent_to_owner" msgid="4620359037192871015">"మీరు మీ కార్యాలయ ప్రొఫైల్కు వెలుపల ఈ అనువర్తనాన్ని ఉపయోగిస్తున్నారు"</string> - <string name="forward_intent_to_work" msgid="3620262405636021151">"మీరు మీ కార్యాలయ ప్రొఫైల్లో ఈ అనువర్తనాన్ని ఉపయోగిస్తున్నారు"</string> + <string name="forward_intent_to_owner" msgid="4620359037192871015">"మీరు మీ కార్యాలయ ప్రొఫైల్కు వెలుపల ఈ యాప్ను ఉపయోగిస్తున్నారు"</string> + <string name="forward_intent_to_work" msgid="3620262405636021151">"మీరు మీ కార్యాలయ ప్రొఫైల్లో ఈ యాప్ను ఉపయోగిస్తున్నారు"</string> <string name="input_method_binding_label" msgid="1166731601721983656">"ఇన్పుట్ పద్ధతి"</string> <string name="sync_binding_label" msgid="469249309424662147">"సమకాలీకరణ"</string> <string name="accessibility_binding_label" msgid="1974602776545801715">"యాక్సెస్ సామర్థ్యం"</string> @@ -1909,7 +1909,7 @@ <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ఈ యాప్ పాత వెర్షన్ Android కోసం రూపొందించబడింది మరియు అది సరిగ్గా పని చేయకపోవచ్చు. అప్డేట్ల కోసం తనిఖీ చేయడానికి ప్రయత్నించండి లేదా డెవలపర్ని సంప్రదించండి."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"అప్డేట్ కోసం తనిఖీ చేయండి"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"మీకు కొత్త సందేశాలు ఉన్నాయి"</string> - <string name="new_sms_notification_content" msgid="3197949934153460639">"వీక్షించడానికి SMS అనువర్తనాన్ని తెరవండి"</string> + <string name="new_sms_notification_content" msgid="3197949934153460639">"వీక్షించడానికి SMS యాప్ను తెరవండి"</string> <string name="profile_encrypted_title" msgid="9001208667521266472">"కొంత ఫంక్షనాలిటీ పరిమితం కావచ్చు"</string> <string name="profile_encrypted_detail" msgid="5279730442756849055">"కార్యాలయ ప్రొఫైల్ లాక్ అయింది"</string> <string name="profile_encrypted_message" msgid="1128512616293157802">"కార్యాలయ ప్రొఫైల్ అన్లాక్ చేయుటకు నొక్కండి"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 8f54c2783c88..b66a80e40524 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -149,8 +149,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"صرف Wi-Fi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <!-- no translation found for crossSimFormat_spn_cross_sim_calling (779976494687695991) --> - <skip /> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> کراس sim کالنگ"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : فارورڈ نہیں کی گئی"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> بعد از <xliff:g id="TIME_DELAY">{2}</xliff:g> سیکنڈ"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index a8bc4ba9a7b6..23719508eaa9 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -57,8 +57,7 @@ <string name="imei" msgid="2157082351232630390">"IMEI"</string> <string name="meid" msgid="3291227361605924674">"MEID"</string> <string name="ClipMmi" msgid="4110549342447630629">"Kiruvchi raqami"</string> - <!-- no translation found for ClirMmi (6752346475055446417) --> - <skip /> + <string name="ClirMmi" msgid="6752346475055446417">"Chaqiruvchi raqamini yashirish"</string> <string name="ColpMmi" msgid="4736462893284419302">"Qo‘ng‘iroq qiluvchining raqami"</string> <string name="ColrMmi" msgid="5889782479745764278">"Qo‘ng‘iroq qiluvchining raqamini cheklash"</string> <string name="CfMmi" msgid="8390012691099787178">"Chaqiruvlarni uzatish"</string> @@ -149,7 +148,7 @@ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Faqat Wi-Fi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> <skip /> - <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Umumiy sim chaqiruvlar"</string> + <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Kross-SIM chaqiruvlar"</string> <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string> <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> soniyadan so‘ng"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index bb2665584600..c16588c4bc7b 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2487,7 +2487,7 @@ <attr name="directBootAware" /> <attr name="visibleToInstantApps" /> <!-- The code for this component is located in the given split. - @deprecated Do not use it. This is not supported. --> + <p>NOTE: This is only applicable to instant app. --> <attr name="splitName" /> </declare-styleable> @@ -2604,7 +2604,7 @@ <attr name="externalService" format="boolean" /> <attr name="visibleToInstantApps" /> <!-- The code for this component is located in the given split. - @deprecated Do not use it. This is not supported. --> + <p>NOTE: This is only applicable to instant app. --> <attr name="splitName" /> <!-- If true, and this is an {@link android.R.attr#isolatedProcess} service, the service will be spawned from an Application Zygote, instead of the regular Zygote. diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index b5580879d328..e46147ea9b7f 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -33,14 +33,14 @@ <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color> <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color> - <color name="accent_device_default_light">@color/system_accent_500</color> + <color name="accent_device_default_light">@color/system_accent_600</color> <color name="accent_device_default_dark">@color/system_accent_200</color> <color name="accent_device_default">@color/accent_device_default_light</color> <color name="background_device_default_dark">@color/system_main_900</color> - <color name="background_device_default_light">@color/system_main_100</color> + <color name="background_device_default_light">@color/system_main_50</color> <color name="background_floating_device_default_dark">@color/system_main_800</color> - <color name="background_floating_device_default_light">@color/system_main_200</color> + <color name="background_floating_device_default_light">@color/system_main_100</color> <!-- Error color --> <color name="error_color_device_default_dark">@color/error_color_material_dark</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 79e221068422..7f6053ea29ae 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1577,6 +1577,10 @@ <item>network</item> </string-array> + <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based + suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. --> + <bool name="config_enableGnssTimeUpdateService">false</bool> + <!-- Enables the TimeZoneRuleManager service. This is the global switch for the updateable time zone update mechanism. --> <bool name="config_enableUpdateableTimeZoneRules">false</bool> @@ -2649,6 +2653,11 @@ <string name="config_usbResolverActivity" translatable="false" >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string> + <!-- Component name of the activity used to inform a user about a sensory being blocked because + of privacy settings. --> + <string name="config_sensorUseStartedActivity" translatable="false" + >com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string> + <!-- Name of the dialog that is used to request the user's consent for a Platform VPN --> <string name="config_platformVpnConfirmDialogComponent" translatable="false" >com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string> diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml index c3cd80b1edda..0ef60c4c9531 100644 --- a/core/res/res/values/dimens_car.xml +++ b/core/res/res/values/dimens_car.xml @@ -17,6 +17,7 @@ --> <resources> <dimen name="car_large_avatar_size">96dp</dimen> + <dimen name="car_large_avatar_badge_size">32dp</dimen> <!-- Application Bar --> <dimen name="car_app_bar_height">80dp</dimen> <!-- Margin --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 317a76fa2913..996fbb366506 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -450,6 +450,8 @@ understand which sub-unit of an application is requesting permissions and using power. [CHAR LIMIT=NONE]--> <string name="offline_location_time_zone_detection_service_attribution">Time Zone Detector (No connectivity)</string> + <!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]--> + <string name="gnss_time_update_service">GNSS Time Update Service</string> <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> @@ -5817,4 +5819,13 @@ ul.</string> <!-- Notification action to dismiss. [CHAR LIMIT=50] --> <string name="dismiss_action">Dismiss</string> + <!--- Content of notification triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=NONE] --> + <string name="sensor_privacy_start_use_mic_notification_content">To continue, <b><xliff:g id="app" example="Gmail">%s</xliff:g></b> needs access to your device microphone.</string> + <!--- Content of notification triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=NONE] --> + <string name="sensor_privacy_start_use_camera_notification_content">To continue, <b><xliff:g id="app" example="Gmail">%s</xliff:g></b> needs access to your device’s camera.</string> + <!--- Action button in the dialog triggered if a sensor (e.g. microphone or camera) is disabled but an app tried to access it. [CHAR LIMIT=60] --> + <string name="sensor_privacy_start_use_dialog_turn_on_button">Turn on</string> + <!--- Label for notification channel for all sensor privacy related notifications. [CHAR LIMIT=NONE] --> + <string name="sensor_privacy_notification_channel_label">Sensor Privacy</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0c86905efbce..e2271d17f29c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -379,6 +379,7 @@ <java-symbol type="string" name="config_usbAccessoryUriActivity" /> <java-symbol type="string" name="config_usbConfirmActivity" /> <java-symbol type="string" name="config_usbResolverActivity" /> + <java-symbol type="string" name="config_sensorUseStartedActivity" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" /> <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" /> @@ -2176,6 +2177,7 @@ <java-symbol type="string" name="config_persistentDataPackageName" /> <java-symbol type="string" name="config_deviceConfiguratorPackageName" /> <java-symbol type="array" name="config_autoTimeSourcesPriority" /> + <java-symbol type="bool" name="config_enableGnssTimeUpdateService" /> <java-symbol type="bool" name="config_enableGeolocationTimeZoneDetection" /> <java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneProvider" /> <java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneOverlay" /> @@ -4150,4 +4152,11 @@ <java-symbol type="bool" name="config_enableBackSound" /> <java-symbol type="bool" name="config_forceOrientationListenerEnabledWhileDreaming" /> + + <java-symbol type="drawable" name="ic_camera_blocked" /> + <java-symbol type="drawable" name="ic_mic_blocked" /> + <java-symbol type="string" name="sensor_privacy_start_use_mic_notification_content" /> + <java-symbol type="string" name="sensor_privacy_start_use_camera_notification_content" /> + <java-symbol type="string" name="sensor_privacy_start_use_dialog_turn_on_button" /> + <java-symbol type="string" name="sensor_privacy_notification_channel_label" /> </resources> diff --git a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java index 1771671db26a..52cc4cac4816 100644 --- a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java +++ b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java @@ -17,11 +17,15 @@ package android.graphics; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; +import android.graphics.fonts.Font; import android.graphics.fonts.FontFileUtil; import android.graphics.fonts.FontVariationAxis; +import android.graphics.fonts.SystemFonts; import android.util.Log; import android.util.Pair; @@ -144,4 +148,18 @@ public class FontFileUtilTest { assertEquals(path + "#" + axes, italic, FontFileUtil.unpackItalic(packed)); } } + + @Test + public void testExtension() throws IOException { + for (Font f : SystemFonts.getAvailableFonts()) { + String name = f.getFile().getName(); + if (!name.endsWith(".ttf") && !name.endsWith(".otf")) { + continue; // Only test ttf/otf file + } + int isOtfFile = FontFileUtil.isPostScriptType1Font(f.getBuffer(), 0); + assertNotEquals(-1, isOtfFile); + String extension = isOtfFile == 1 ? ".otf" : ".ttf"; + assertTrue(name.endsWith(extension)); + } + } } diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java index 0f17d27048f3..6be9306bbd2d 100644 --- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java @@ -254,7 +254,7 @@ public class DateIntervalFormatTest { assertEquals("19–22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 de ene. – jue., 22 de ene. de 2009", + assertEquals("lun, 19 de ene. – jue, 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", @@ -265,7 +265,7 @@ public class DateIntervalFormatTest { assertEquals("19 de ene. – 22 de abr. 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 de ene. – mié., 22 de abr. de 2009", + assertEquals("lun, 19 de ene. – mié, 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", @@ -286,9 +286,9 @@ public class DateIntervalFormatTest { assertEquals("19–22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19–22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, + assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene. – jue., 22 ene. 2009", + assertEquals("lun, 19 ene – jue, 22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", @@ -296,19 +296,19 @@ public class DateIntervalFormatTest { assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene. – 22 abr. 2009", + assertEquals("19 ene – 22 abr 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene. – mié., 22 abr. 2009", + assertEquals("lun, 19 ene – mié, 22 abr 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. 2009 – 9 feb. 2012", + assertEquals("19 ene 2009 – 9 feb 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("ene. 2009 – feb. 2012", + assertEquals("ene 2009 – feb 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); assertEquals("19 de enero de 2009–9 de febrero de 2012", diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index 068d04798858..5612833e5ddd 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -212,7 +212,7 @@ public class FormatterTest { // Make sure it works on different locales. setLocale(new Locale("ru", "RU")); - assertEquals("1 мин.", Formatter.formatShortElapsedTimeRoundingUpToMinutes( + assertEquals("1 мин", Formatter.formatShortElapsedTimeRoundingUpToMinutes( mContext, 1 * SECOND)); } diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java index 4b3b5735b4f3..b3425162f48f 100644 --- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java +++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java @@ -755,8 +755,8 @@ public class RelativeDateTimeFormatterTest { final Locale locale = new Locale("fr"); android.icu.text.RelativeDateTimeFormatter icuFormatter = android.icu.text.RelativeDateTimeFormatter.getInstance(locale); - assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T")); + assertEquals("D, T", icuFormatter.combineDateAndTime("D", "T")); // Ensure single quote ' and curly braces {} are not interpreted in input values. - assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); + assertEquals("D'x', T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java index 07eeae009b80..e1b403fdefcf 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java @@ -39,7 +39,8 @@ public final class ConversationActionTest { @Test public void toBuilder() { final Context context = InstrumentationRegistry.getTargetContext(); - final PendingIntent intent = PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED); + final PendingIntent intent = PendingIntent.getActivity( + context, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); final Icon icon = Icon.createWithData(new byte[]{0}, 0, 1); final Bundle extras = new Bundle(); extras.putInt("key", 5); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index db62e171c590..c393d68c3f94 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -16,6 +16,8 @@ package android.view.textclassifier; +import static android.app.PendingIntent.FLAG_IMMUTABLE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -79,7 +81,7 @@ public class TextClassificationTest { final String primaryDescription = "primaryDescription"; final Intent primaryIntent = new Intent("primaryIntentAction"); final PendingIntent primaryPendingIntent = PendingIntent.getActivity(context, 0, - primaryIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED); + primaryIntent, FLAG_IMMUTABLE); final RemoteAction remoteAction0 = new RemoteAction(primaryIcon, primaryLabel, primaryDescription, primaryPendingIntent); @@ -88,7 +90,7 @@ public class TextClassificationTest { final String secondaryDescription = "secondaryDescription"; final Intent secondaryIntent = new Intent("secondaryIntentAction"); final PendingIntent secondaryPendingIntent = PendingIntent.getActivity(context, 0, - secondaryIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED); + secondaryIntent, FLAG_IMMUTABLE); final RemoteAction remoteAction1 = new RemoteAction(secondaryIcon, secondaryLabel, secondaryDescription, secondaryPendingIntent); @@ -156,7 +158,7 @@ public class TextClassificationTest { final int iconColor = Color.RED; final String label = "label"; final PendingIntent pendingIntent = PendingIntent.getActivity( - context, 0, new Intent("ACTION_0"), PendingIntent.FLAG_MUTABLE_UNAUDITED); + context, 0, new Intent("ACTION_0"), FLAG_IMMUTABLE); final RemoteAction remoteAction = new RemoteAction( generateTestIcon(width, height, iconColor), label, @@ -239,7 +241,8 @@ public class TextClassificationTest { .setIntent(new Intent("action")) .setOnClickListener(view -> { }) .addAction(new RemoteAction(icon1, "title1", "desc1", - PendingIntent.getActivity(context, 0, new Intent("action1"), PendingIntent.FLAG_MUTABLE_UNAUDITED))) + PendingIntent.getActivity( + context, 0, new Intent("action1"), FLAG_IMMUTABLE))) .addAction(new RemoteAction(icon1, "title2", "desc2", PendingIntent.getActivity(context, 0, new Intent("action2"), 0))) .setEntityType(TextClassifier.TYPE_EMAIL, 0.5f) diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index 5def552c72b4..14c077ce06f4 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -16,6 +16,8 @@ package android.view.textclassifier; +import static android.app.PendingIntent.FLAG_IMMUTABLE; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -128,7 +130,8 @@ public class TextSelectionTest { final TextClassification classification = new TextClassification.Builder() .addAction(new RemoteAction(icon1, "title1", "desc1", - PendingIntent.getActivity(context, 0, new Intent("action1"), 0))) + PendingIntent.getActivity( + context, 0, new Intent("action1"), FLAG_IMMUTABLE))) .setEntityType(TextClassifier.TYPE_ADDRESS, 1.0f) .build(); final TextSelection textSelection = new TextSelection.Builder(startIndex, endIndex) diff --git a/core/tests/coretests/src/android/view/textservice/OWNERS b/core/tests/coretests/src/android/view/textservice/OWNERS new file mode 100644 index 000000000000..0471e29a25cd --- /dev/null +++ b/core/tests/coretests/src/android/view/textservice/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 816455 + +include /services/core/java/com/android/server/textservices/OWNERS 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 8ff318e8d555..dc332533b2ca 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -58,6 +58,7 @@ import org.junit.runners.Suite; KernelWakelockReaderTest.class, LongSamplingCounterTest.class, LongSamplingCounterArrayTest.class, + MobileRadioPowerCalculatorTest.class, PowerCalculatorTest.class, PowerProfileTest.class, ScreenPowerCalculatorTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index 55f64f977933..59534e4a84ef 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -16,10 +16,13 @@ package com.android.internal.os; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.net.NetworkStats; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; @@ -32,6 +35,7 @@ import androidx.test.InstrumentationRegistry; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import org.mockito.stubbing.Answer; public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; @@ -53,6 +57,14 @@ public class BatteryUsageStatsRule implements TestRule { public BatteryUsageStatsRule setAveragePower(String key, double value) { when(mPowerProfile.getAveragePower(key)).thenReturn(value); + when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value); + return this; + } + + public BatteryUsageStatsRule setAveragePowerUnspecified(String key) { + when(mPowerProfile.getAveragePower(key)).thenReturn(0.0); + when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())) + .thenAnswer((Answer<Double>) invocation -> (Double) invocation.getArguments()[1]); return this; } @@ -64,6 +76,10 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + public void setNetworkStats(NetworkStats networkStats) { + mBatteryStats.setNetworkStats(networkStats); + } + @Override public Statement apply(Statement base, Description description) { return new Statement() { @@ -76,6 +92,7 @@ public class BatteryUsageStatsRule implements TestRule { } private void noteOnBattery() { + mBatteryStats.setOnBatteryInternal(true); mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); } diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java new file mode 100644 index 000000000000..4230066f32a1 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java @@ -0,0 +1,108 @@ +/* + * 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; +import android.telephony.DataConnectionRealTimeInfo; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; + +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 MobileRadioPowerCalculatorTest { + 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() + .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE) + .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON) + .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0) + .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0) + .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0) + .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, + new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0}); + + @Test + public void testCounterBasedModel() { + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + + // Scan for a cell + stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, + TelephonyManager.SIM_STATE_READY, + 2000, 2000); + + // Found a cell + stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY, + 5000, 5000); + + // Note cell signal strength + SignalStrength signalStrength = mock(SignalStrength.class); + when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE); + stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000); + + stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + 8_000_000_000L, APP_UID, 8000, 8000); + + // Note established network + stats.noteNetworkInterfaceType("cellular", ConnectivityManager.TYPE_MOBILE); + + // Note application network activity + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100); + mStatsRule.setNetworkStats(networkStats); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[] {100, 200, 300, 400, 500}, 600); + stats.noteModemControllerActivity(mai, 10000, 10000); + + mStatsRule.setTime(12_000_000, 12_000_000); + + MobileRadioPowerCalculator calculator = + new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + SystemBatteryConsumer consumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(1.44440); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(0.8); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index fc237219be51..bf74c8d82fa2 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -16,7 +16,7 @@ package com.android.internal.os; -import android.location.GnssSignalQuality; +import android.net.NetworkStats; import android.os.Handler; import android.os.Looper; import android.util.SparseIntArray; @@ -38,26 +38,14 @@ import java.util.concurrent.Future; public class MockBatteryStatsImpl extends BatteryStatsImpl { public BatteryStatsImpl.Clocks clocks; public boolean mForceOnBattery; + private NetworkStats mNetworkStats; MockBatteryStatsImpl(Clocks clocks) { super(clocks); this.clocks = mClocks; - mScreenOnTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null, - mOnBatteryTimeBase); - mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null, - mOnBatteryTimeBase); - for (int i = 0; i < mScreenBrightnessTimer.length; i++) { - mScreenBrightnessTimer[i] = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null, - mOnBatteryTimeBase); - } - mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); - mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, 1); - setExternalStatsSyncLocked(new DummyExternalStatsSync()); + initTimersAndCounters(); - for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) { - mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null, - mOnBatteryTimeBase); - } + setExternalStatsSyncLocked(new DummyExternalStatsSync()); final boolean[] supportedBuckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS]; Arrays.fill(supportedBuckets, true); @@ -106,6 +94,16 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase; } + public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) { + mNetworkStats = networkStats; + return this; + } + + @Override + protected NetworkStats readNetworkStatsLocked(String[] ifaces) { + return mNetworkStats; + } + public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) { mPowerProfile = powerProfile; return this; diff --git a/core/tests/overlaytests/device/Android.bp b/core/tests/overlaytests/device/Android.bp index 12a2b0815050..f86ac9ce37e1 100644 --- a/core/tests/overlaytests/device/Android.bp +++ b/core/tests/overlaytests/device/Android.bp @@ -16,7 +16,11 @@ android_test { name: "OverlayDeviceTests", srcs: ["src/**/*.java"], platform_apis: true, - static_libs: ["androidx.test.rules"], + certificate: "platform", + static_libs: [ + "androidx.test.rules", + "testng", + ], test_suites: ["device-tests"], data: [ ":OverlayDeviceTests_AppOverlayOne", diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml index 4881636c7095..a69911f8d827 100644 --- a/core/tests/overlaytests/device/AndroidManifest.xml +++ b/core/tests/overlaytests/device/AndroidManifest.xml @@ -19,6 +19,8 @@ <uses-sdk android:minSdkVersion="21" /> + <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" /> + <application> <uses-library android:name="android.test.runner"/> </application> diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml index 6507839a4288..ebbdda559ed2 100644 --- a/core/tests/overlaytests/device/AndroidTest.xml +++ b/core/tests/overlaytests/device/AndroidTest.xml @@ -19,9 +19,20 @@ <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-instrumentation" /> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="remount-system" value="true" /> + <option name="push" value="OverlayDeviceTests.apk->/system/app/OverlayDeviceTests.apk" /> + </target_preparer> + + <!-- Reboot to have the test APK scanned by PM and reboot after to remove the test APK. --> + <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer"> + <option name="pre-reboot" value="true" /> + <option name="post-reboot" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="OverlayDeviceTests.apk" /> <option name="test-file-name" value="OverlayDeviceTests_AppOverlayOne.apk" /> <option name="test-file-name" value="OverlayDeviceTests_AppOverlayTwo.apk" /> <option name="test-file-name" value="OverlayDeviceTests_FrameworkOverlay.apk" /> diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java index 390bb766ab81..76c01a7e1125 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java @@ -18,60 +18,76 @@ package com.android.overlaytest; import static java.util.concurrent.TimeUnit.SECONDS; -import android.app.UiAutomation; -import android.content.res.Resources; -import android.os.ParcelFileDescriptor; +import android.annotation.NonNull; +import android.content.Context; +import android.content.om.OverlayManager; +import android.content.om.OverlayManagerTransaction; +import android.os.UserHandle; import androidx.test.InstrumentationRegistry; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; class LocalOverlayManager { private static final long TIMEOUT = 30; - public static void setEnabledAndWait(Executor executor, final String packageName, - boolean enable) throws Exception { - final String pattern = (enable ? "[x]" : "[ ]") + " " + packageName; - if (executeShellCommand("cmd overlay list").contains(pattern)) { - // nothing to do, overlay already in the requested state - return; + public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable, + @NonNull final String[] overlaysToDisable) throws Exception { + final int userId = UserHandle.myUserId(); + OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder(); + for (String pkg : overlaysToEnable) { + builder.setEnabled(pkg, true, userId); } + for (String pkg : overlaysToDisable) { + builder.setEnabled(pkg, false, userId); + } + OverlayManagerTransaction transaction = builder.build(); - final Resources res = InstrumentationRegistry.getContext().getResources(); - final String[] oldApkPaths = res.getAssets().getApkPaths(); + final Context ctx = InstrumentationRegistry.getTargetContext(); FutureTask<Boolean> task = new FutureTask<>(() -> { while (true) { - if (!Arrays.equals(oldApkPaths, res.getAssets().getApkPaths())) { + final String[] paths = ctx.getResources().getAssets().getApkPaths(); + if (arrayTailContains(paths, overlaysToEnable) + && arrayDoesNotContain(paths, overlaysToDisable)) { return true; } Thread.sleep(10); } }); + + OverlayManager om = ctx.getSystemService(OverlayManager.class); + om.commit(transaction); + + Executor executor = (cmd) -> new Thread(cmd).start(); executor.execute(task); - executeShellCommand("cmd overlay " + (enable ? "enable " : "disable ") + packageName); task.get(TIMEOUT, SECONDS); } - private static String executeShellCommand(final String command) - throws Exception { - final UiAutomation uiAutomation = - InstrumentationRegistry.getInstrumentation().getUiAutomation(); - final ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command); - try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { - final BufferedReader reader = new BufferedReader( - new InputStreamReader(in, StandardCharsets.UTF_8)); - StringBuilder str = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - str.append(line); + private static boolean arrayTailContains(@NonNull final String[] array, + @NonNull final String[] substrings) { + if (array.length < substrings.length) { + return false; + } + for (int i = 0; i < substrings.length; i++) { + String a = array[array.length - substrings.length + i]; + String s = substrings[i]; + if (!a.contains(s)) { + return false; + } + } + return true; + } + + private static boolean arrayDoesNotContain(@NonNull final String[] array, + @NonNull final String[] substrings) { + for (String s : substrings) { + for (String a : array) { + if (a.contains(s)) { + return false; + } } - return str.toString(); } + return true; } } diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java new file mode 100644 index 000000000000..0b4f5e227169 --- /dev/null +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.testng.Assert.assertThrows; + +import android.content.Context; +import android.content.om.OverlayInfo; +import android.content.om.OverlayManager; +import android.content.om.OverlayManagerTransaction; +import android.content.res.Resources; +import android.os.UserHandle; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +@MediumTest +public class TransactionTest { + static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one"; + static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two"; + + private Context mContext; + private Resources mResources; + private OverlayManager mOverlayManager; + private int mUserId; + private UserHandle mUserHandle; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mResources = mContext.getResources(); + mOverlayManager = mContext.getSystemService(OverlayManager.class); + mUserId = UserHandle.myUserId(); + mUserHandle = UserHandle.of(mUserId); + + LocalOverlayManager.toggleOverlaysAndWait( + new String[]{}, + new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG}); + } + + @Test + public void testValidTransaction() throws Exception { + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId); + + OverlayManagerTransaction t = new OverlayManagerTransaction.Builder() + .setEnabled(APP_OVERLAY_ONE_PKG, true) + .setEnabled(APP_OVERLAY_TWO_PKG, true) + .build(); + mOverlayManager.commit(t); + + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, true, mUserId); + List<OverlayInfo> ois = + mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle); + assertEquals(ois.size(), 2); + assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG); + assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG); + + OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder() + .setEnabled(APP_OVERLAY_TWO_PKG, true) + .setEnabled(APP_OVERLAY_ONE_PKG, true) + .build(); + mOverlayManager.commit(t2); + + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, true, mUserId); + List<OverlayInfo> ois2 = + mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle); + assertEquals(ois2.size(), 2); + assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG); + assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG); + + OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder() + .setEnabled(APP_OVERLAY_TWO_PKG, false) + .build(); + mOverlayManager.commit(t3); + + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId); + List<OverlayInfo> ois3 = + mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle); + assertEquals(ois3.size(), 2); + assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG); + assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG); + } + + @Test + public void testInvalidRequestHasNoEffect() { + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId); + + OverlayManagerTransaction t = new OverlayManagerTransaction.Builder() + .setEnabled(APP_OVERLAY_ONE_PKG, true) + .setEnabled("does-not-exist", true) + .setEnabled(APP_OVERLAY_TWO_PKG, true) + .build(); + assertThrows(SecurityException.class, () -> mOverlayManager.commit(t)); + + assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId); + assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId); + } + + private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) { + final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId)); + assertNotNull(oi); + assertEquals(oi.isEnabled(), enabled); + } +} diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java index d28c47d9c922..420f755c5251 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java @@ -22,8 +22,6 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.concurrent.Executor; - @RunWith(JUnit4.class) @MediumTest public class WithMultipleOverlaysTest extends OverlayBaseTest { @@ -33,9 +31,8 @@ public class WithMultipleOverlaysTest extends OverlayBaseTest { @BeforeClass public static void enableOverlay() throws Exception { - Executor executor = (cmd) -> new Thread(cmd).start(); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, true); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, true); - LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, true); + LocalOverlayManager.toggleOverlaysAndWait( + new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG}, + new String[]{}); } } diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java index 6566ad304c1c..a86255e96388 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java @@ -22,8 +22,6 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.concurrent.Executor; - @RunWith(JUnit4.class) @MediumTest public class WithOverlayTest extends OverlayBaseTest { @@ -32,10 +30,9 @@ public class WithOverlayTest extends OverlayBaseTest { } @BeforeClass - public static void enableOverlay() throws Exception { - Executor executor = (cmd) -> new Thread(cmd).start(); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, true); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, false); - LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, true); + public static void enableOverlays() throws Exception { + LocalOverlayManager.toggleOverlaysAndWait( + new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG}, + new String[]{APP_OVERLAY_TWO_PKG}); } } diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java index 48cfeab2fbff..51c411819b87 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java @@ -22,8 +22,6 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.concurrent.Executor; - @RunWith(JUnit4.class) @MediumTest public class WithoutOverlayTest extends OverlayBaseTest { @@ -33,9 +31,8 @@ public class WithoutOverlayTest extends OverlayBaseTest { @BeforeClass public static void disableOverlays() throws Exception { - Executor executor = (cmd) -> new Thread(cmd).start(); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, false); - LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, false); - LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, false); + LocalOverlayManager.toggleOverlaysAndWait( + new String[]{}, + new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG}); } } diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp index da3aa007135a..847b4915530b 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp +++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp @@ -15,6 +15,6 @@ android_test { name: "OverlayDeviceTests_AppOverlayOne", sdk_version: "current", - + certificate: "platform", aaptflags: ["--no-resource-removal"], } diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp index 215b66da36dc..7d5f82a71b44 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp +++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp @@ -15,6 +15,6 @@ android_test { name: "OverlayDeviceTests_AppOverlayTwo", sdk_version: "current", - + certificate: "platform", aaptflags: ["--no-resource-removal"], } diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml index 17d614e3409c..99c38dbe6ac9 100644 --- a/data/etc/com.android.launcher3.xml +++ b/data/etc/com.android.launcher3.xml @@ -20,5 +20,6 @@ <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/> <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/> </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 491433637e67..3aaf11cef17c 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -28,11 +28,12 @@ public final class BLASTBufferQueue { public long mNativeObject; // BLASTBufferQueue* private static native long nativeCreate(String name, long surfaceControl, long width, - long height, boolean tripleBufferingEnabled); + long height, int format, boolean tripleBufferingEnabled); private static native void nativeDestroy(long ptr); private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle); private static native void nativeSetNextTransaction(long ptr, long transactionPtr); - private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height); + private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height, + int format); private static native void nativeFlushShadowQueue(long ptr); private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr, long frameNumber); @@ -52,8 +53,9 @@ public final class BLASTBufferQueue { /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, - boolean tripleBufferingEnabled) { - mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, tripleBufferingEnabled); + @PixelFormat.Format int format, boolean tripleBufferingEnabled) { + mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format, + tripleBufferingEnabled); } public void destroy() { @@ -85,8 +87,15 @@ public final class BLASTBufferQueue { nativeSetNextTransaction(mNativeObject, t == null ? 0 : t.mNativeObject); } - public void update(SurfaceControl sc, int width, int height) { - nativeUpdate(mNativeObject, sc.mNativeObject, width, height); + /** + * Updates {@link SurfaceControl}, size, and format for a particular BLASTBufferQueue + * @param sc The new SurfaceControl that this BLASTBufferQueue will update + * @param width The new width for the buffer. + * @param height The new height for the buffer. + * @param format The new format for the buffer. + */ + public void update(SurfaceControl sc, int width, int height, @PixelFormat.Format int format) { + nativeUpdate(mNativeObject, sc.mNativeObject, width, height, format); } /** diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index 2896c46b119c..af49619fb840 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -166,6 +166,23 @@ public class FontFileUtil { return nGetFontPostScriptName(buffer, index); } + /** + * Analyze name OpenType table and return true if the font has PostScript Type 1 glyphs. + * + * IllegalArgumentException will be thrown for invalid font data. + * -1 will be returned if the byte buffer is not a OpenType font data. + * 0 will be returned if the font file doesn't have PostScript Type 1 glyphs, i.e. ttf file. + * 1 will be returned if the font file has PostScript Type 1 glyphs, i.e. otf file. + * + * @param buffer a buffer of OpenType font + * @param index a font index + * @return a post script name or null if it is invalid or not found. + */ + public static int isPostScriptType1Font(@NonNull ByteBuffer buffer, + @IntRange(from = 0) int index) { + return nIsPostScriptType1Font(buffer, index); + } + @FastNative private static native long nGetFontRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index); @@ -173,4 +190,8 @@ public class FontFileUtil { @FastNative private static native String nGetFontPostScriptName(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index); + + @FastNative + private static native int nIsPostScriptType1Font(@NonNull ByteBuffer buffer, + @IntRange(from = 0) int index); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 54167b4b3042..6aa50dbd078e 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -50,9 +50,11 @@ public final class SystemFonts { private static final String DEFAULT_FAMILY = "sans-serif"; private static final String FONTS_XML = "/system/etc/fonts.xml"; - private static final String SYSTEM_FONT_DIR = "/system/fonts/"; + /** @hide */ + public static final String SYSTEM_FONT_DIR = "/system/fonts/"; private static final String OEM_XML = "/product/etc/fonts_customization.xml"; - private static final String OEM_FONT_DIR = "/product/fonts/"; + /** @hide */ + public static final String OEM_FONT_DIR = "/product/fonts/"; private SystemFonts() {} // Do not instansiate. diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 1fde2b5412ed..fcc518c374e3 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -17,11 +17,13 @@ package android.security; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.security.keymint.HardwareAuthToken; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.security.authorization.IKeystoreAuthorization; +import android.security.authorization.LockScreenEvent; import android.system.keystore2.ResponseCode; import android.util.Log; @@ -75,4 +77,31 @@ public class Authorization { return addAuthToken(AuthTokenUtils.toHardwareAuthToken(authToken)); } + /** + * Informs keystore2 about lock screen event. + * + * @param locked - whether it is a lock (true) or unlock (false) event + * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic + * password provided by the LockSettingService + * + * @return 0 if successful or a {@code ResponseCode}. + */ + public int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId, + @Nullable byte[] syntheticPassword) { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0; + try { + if (locked) { + getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null); + } else { + getService().onLockScreenEvent(LockScreenEvent.UNLOCK, userId, syntheticPassword); + } + return 0; + } catch (RemoteException e) { + Log.w(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index b43203dbfd77..0958a070c82d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -100,7 +100,7 @@ public class ShellInitImpl { mSplitScreenOptional.ifPresent(SplitScreen::onOrganizerRegistered); // Bind the splitscreen impl to the drag drop controller - mDragAndDropController.initialize(mLegacySplitScreenOptional); + mDragAndDropController.initialize(mSplitScreenOptional); if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index c7219089bffe..16cd3cf3686c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -85,7 +85,7 @@ class BubbleOverflow( bitmapSize = positioner.bubbleBitmapSize iconBitmapSize = (bitmapSize * 0.46f).toInt() val bubbleSize = positioner.bubbleSize - overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize)) + overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize) expandedView?.updateDimensions() } @@ -95,7 +95,7 @@ class BubbleOverflow( // Set overflow button accent color, dot color val typedValue = TypedValue() context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) - val colorAccent = res.getColor(typedValue.resourceId) + val colorAccent = res.getColor(typedValue.resourceId, null) overflowBtn?.drawable?.setTint(colorAccent) dotColor = colorAccent @@ -105,7 +105,7 @@ class BubbleOverflow( val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) val bg = ColorDrawable(res.getColor( - if (nightMode) R.color.bubbles_dark else R.color.bubbles_light)) + if (nightMode) R.color.bubbles_dark else R.color.bubbles_light, null)) val fg = InsetDrawable(overflowBtn?.drawable, bitmapSize - iconBitmapSize /* inset */) @@ -115,7 +115,7 @@ class BubbleOverflow( // Update dot path dotPath = PathParser.createPathFromPathData( res.getString(com.android.internal.R.string.config_icon_mask)) - val scale = iconFactory.normalizer.getScale(getIconView()!!.getDrawable(), + val scale = iconFactory.normalizer.getScale(iconView!!.drawable, null /* outBounds */, null /* path */, null /* outMaskShape */) val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f val matrix = Matrix() @@ -176,10 +176,10 @@ class BubbleOverflow( overflowBtn = inflater.inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */) as BadgedImageView overflowBtn?.initialize(positioner) - overflowBtn?.setContentDescription(context.resources.getString( - R.string.bubble_overflow_button_content_description)) + overflowBtn?.contentDescription = context.resources.getString( + R.string.bubble_overflow_button_content_description) val bubbleSize = positioner.bubbleSize - overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize)) + overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize) updateBtnTheme() } return overflowBtn @@ -190,10 +190,10 @@ class BubbleOverflow( } override fun getTaskId(): Int { - return if (expandedView != null) expandedView!!.getTaskId() else INVALID_TASK_ID + return if (expandedView != null) expandedView!!.taskId else INVALID_TASK_ID } companion object { - @JvmField val KEY = "Overflow" + const val KEY = "Overflow" } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt index b34732901648..a1b0dbe0a6dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles import android.graphics.PointF import android.os.Handler +import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker import android.view.View @@ -90,7 +91,7 @@ abstract class RelativeTouchListener : View.OnTouchListener { private var touchSlop: Int = -1 private var movedEnough = false - private val handler = Handler() + private val handler = Handler(Looper.myLooper()!!) private var performedLongClick = false @Suppress("UNCHECKED_CAST") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt index b4d738712893..9f6dd1f27b62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt @@ -460,9 +460,9 @@ abstract class MagnetizedObject<T : Any>( /** Plays the given vibration effect if haptics are enabled. */ @SuppressLint("MissingPermission") - private fun vibrateIfEnabled(effect: Int) { + private fun vibrateIfEnabled(effectId: Int) { if (hapticsEnabled && systemHapticsEnabled) { - vibrator.vibrate(effect.toLong()) + vibrator.vibrate(VibrationEffect.createPredefined(effectId)) } } 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 e17a943b5cb0..c27c92961c2b 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 @@ -135,7 +135,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; final DividerSnapAlgorithm.SnapTarget snapTarget = - mSplitLayout.findSnapTarget(position, velocity); + mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */); mSplitLayout.snapToTarget(position, snapTarget); break; } 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 d77def54c87e..60231df37370 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 @@ -188,11 +188,11 @@ public class SplitLayout { public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.flag) { case FLAG_DISMISS_START: - mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */); + mLayoutChangeListener.onSnappedToDismiss(false /* bottomOrRight */); mSplitWindowManager.setResizingSplits(false); break; case FLAG_DISMISS_END: - mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */); + mLayoutChangeListener.onSnappedToDismiss(true /* bottomOrRight */); mSplitWindowManager.setResizingSplits(false); break; default: @@ -207,9 +207,11 @@ public class SplitLayout { /** * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity. + * If hardDismiss is set to {@code true}, it will be harder to reach dismiss target. */ - public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) { - return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity); + public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity, + boolean hardDismiss) { + return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index c8b4e10d4bb6..c8938ad40aba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -52,7 +52,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen; import java.util.Optional; @@ -66,7 +66,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange private final Context mContext; private final DisplayController mDisplayController; - private LegacySplitScreen mLegacySplitScreen; + private SplitScreen mSplitScreen; private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @@ -76,8 +76,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange mDisplayController = displayController; } - public void initialize(Optional<LegacySplitScreen> splitscreen) { - mLegacySplitScreen = splitscreen.orElse(null); + public void initialize(Optional<SplitScreen> splitscreen) { + mSplitScreen = splitscreen.orElse(null); mDisplayController.addDisplayWindowListener(this); } @@ -104,7 +104,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange R.layout.global_drop_target, null); rootView.setOnDragListener(this); rootView.setVisibility(View.INVISIBLE); - DragLayout dragLayout = new DragLayout(context, mLegacySplitScreen); + DragLayout dragLayout = new DragLayout(context, mSplitScreen); rootView.addView(dragLayout, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 4043d0bd3173..800150c0a93c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -34,6 +34,8 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -59,13 +61,12 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; /** * The policy for handling drag and drop operations to shell. @@ -77,22 +78,22 @@ public class DragAndDropPolicy { private final Context mContext; private final ActivityTaskManager mActivityTaskManager; private final Starter mStarter; - private final LegacySplitScreen mLegacySplitScreen; + private final SplitScreen mSplitScreen; private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); private DragSession mSession; - public DragAndDropPolicy(Context context, LegacySplitScreen legacySplitScreen) { - this(context, ActivityTaskManager.getInstance(), legacySplitScreen, - new DefaultStarter(context, legacySplitScreen)); + public DragAndDropPolicy(Context context, SplitScreen splitScreen) { + this(context, ActivityTaskManager.getInstance(), splitScreen, + new DefaultStarter(context, splitScreen)); } @VisibleForTesting DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager, - LegacySplitScreen legacySplitScreen, Starter starter) { + SplitScreen splitScreen, Starter starter) { mContext = context; mActivityTaskManager = activityTaskManager; - mLegacySplitScreen = legacySplitScreen; + mSplitScreen = splitScreen; mStarter = starter; } @@ -122,64 +123,54 @@ public class DragAndDropPolicy { final int ih = h - insets.top - insets.bottom; final int l = insets.left; final int t = insets.top; - final boolean isVerticalSplit = mSession.isPhone && !mSession.displayLayout.isLandscape(); - if (mSession.dragItemSupportsSplitscreen - && mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD - && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN - && mSession.runningTaskIsResizeable) { - // Allow splitting when there is a fullscreen standard activity running - if (isVerticalSplit) { - // TODO(b/169894807): For now, only allow splitting to the right/bottom until we - // have split pairs - mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(l, t, l + iw, t + ih / 2), - new Rect(l, t, l + iw, t + ih), - new Rect(0, 0, w, h))); - mTargets.add(new Target(TYPE_SPLIT_BOTTOM, - new Rect(l, t + ih / 2, l + iw, t + ih), - new Rect(l, t + ih / 2, l + iw, t + ih), - new Rect(0, h / 2, w, h))); - } else { - mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(l, t, l + iw / 2, t + ih), - new Rect(l, t, l + iw, t + ih), - new Rect(0, 0, w, h))); - mTargets.add(new Target(TYPE_SPLIT_RIGHT, - new Rect(l + iw / 2, t, l + iw, t + ih), - new Rect(l + iw / 2, t, l + iw, t + ih), - new Rect(w / 2, 0, w, h))); - } - } else if (mSession.dragItemSupportsSplitscreen - && mLegacySplitScreen != null - && mLegacySplitScreen.isDividerVisible()) { + final Rect displayRegion = new Rect(l, t, l + iw, t + ih); + final Rect fullscreenDrawRegion = new Rect(displayRegion); + final Rect fullscreenHitRegion = new Rect(displayRegion); + final boolean inLandscape = mSession.displayLayout.isLandscape(); + final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible(); + // We allow splitting if we are already in split-screen or the running task is a standard + // task in fullscreen mode. + final boolean allowSplit = inSplitScreen + || (mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD + && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN); + if (allowSplit) { // Already split, allow replacing existing split task - // TODO(b/169894807): For now, only allow replacing the non-primary task until we have - // split pairs - final Rect secondarySplitRawBounds = - mLegacySplitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds(); - final Rect secondarySplitBounds = new Rect(secondarySplitRawBounds); - secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih)); - if (isVerticalSplit) { - mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(l, t, l + iw, secondarySplitRawBounds.top), - new Rect(l, t, l + iw, t + ih), - new Rect(0, 0, w, secondarySplitRawBounds.top))); + final Rect topOrLeftBounds = new Rect(); + final Rect bottomOrRightBounds = new Rect(); + mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds); + topOrLeftBounds.intersect(displayRegion); + bottomOrRightBounds.intersect(displayRegion); + + if (inLandscape) { + final Rect leftHitRegion = new Rect(); + final Rect leftDrawRegion = topOrLeftBounds; + final Rect rightHitRegion = new Rect(); + final Rect rightDrawRegion = bottomOrRightBounds; + + displayRegion.splitVertically(leftHitRegion, fullscreenHitRegion, rightHitRegion); + + mTargets.add( + new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion)); + } else { - mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(l, t, secondarySplitRawBounds.left, t + ih), - new Rect(l, t, l + iw, t + ih), - new Rect(0, 0, w, h))); + final Rect topHitRegion = new Rect(); + final Rect topDrawRegion = topOrLeftBounds; + final Rect bottomHitRegion = new Rect(); + final Rect bottomDrawRegion = bottomOrRightBounds; + + displayRegion.splitHorizontally( + topHitRegion, fullscreenHitRegion, bottomHitRegion); + + mTargets.add( + new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion)); } - mTargets.add(new Target(isVerticalSplit ? TYPE_SPLIT_BOTTOM : TYPE_SPLIT_RIGHT, - new Rect(secondarySplitBounds), - new Rect(secondarySplitBounds), - new Rect(secondarySplitBounds))); } else { - // Otherwise only show the fullscreen target - mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(l, t, l + iw, t + ih), - new Rect(l, t, l + iw, t + ih), - new Rect(0, 0, w, h))); + // Split-screen not allowed, so only show the fullscreen target + mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion)); } return mTargets; } @@ -208,58 +199,34 @@ public class DragAndDropPolicy { final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK); final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT); final Intent dragData = mSession.dragData; + final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible(); + final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT; + final Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS) + ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) + : new Bundle(); - boolean deferAppLaunchUntilSplit = false; if (target.type == TYPE_FULLSCREEN) { - if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) { - // If in split, remove split and launch fullscreen - mStarter.exitSplitScreen(mSession.runningTaskId); - } else { - // Not in split, fall through to launch - } - } else { - if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) { - // Split is already visible, just replace the task - // TODO(b/169894807): Since we only allow replacing the non-primary target above - // just fall through and start the activity - } else { - // Not in split, enter split now - mStarter.enterSplitScreen(mSession.runningTaskId, - target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP); - deferAppLaunchUntilSplit = true; + // Exit split stages if needed + mStarter.exitSplitScreen(); + } else if (mSplitScreen != null) { + // Update launch options for the split side we are targeting. + final int position = leftOrTop + ? SIDE_STAGE_POSITION_TOP_OR_LEFT : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; + if (!inSplitScreen) { + // Update the side stage position to match where we want to launch. + mSplitScreen.setSideStagePosition(position); } + mSplitScreen.updateActivityOptions(opts, position); } - final Runnable startAppRunnable = () -> { - Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS) - ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) - : null; - if (isTask) { - mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts); - } else if (isShortcut) { - mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME), - dragData.getStringExtra(EXTRA_SHORTCUT_ID), - opts, dragData.getParcelableExtra(EXTRA_USER)); - } else { - mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts); - } - }; - if (deferAppLaunchUntilSplit) { - // TODO(b/169894807): The enterSplitScreen() call above will trigger the current task - // into split, and we should wait for home and other tasks to be moved to - // split-secondary before trying to launch the new secondary task. This can be removed - // once we have app-pairs. - mLegacySplitScreen.registerInSplitScreenListener(new Consumer<Boolean>() { - @Override - public void accept(Boolean inSplit) { - if (inSplit) { - startAppRunnable.run(); - mLegacySplitScreen.unregisterInSplitScreenListener(this); - } - } - }); + if (isTask) { + mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts); + } else if (isShortcut) { + mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME), + dragData.getStringExtra(EXTRA_SHORTCUT_ID), + opts, dragData.getParcelableExtra(EXTRA_USER)); } else { - startAppRunnable.run(); + mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts); } } @@ -323,7 +290,7 @@ public class DragAndDropPolicy { UserHandle user); void startIntent(PendingIntent intent, Bundle activityOptions); void enterSplitScreen(int taskId, boolean leftOrTop); - void exitSplitScreen(int taskId); + void exitSplitScreen(); } /** @@ -332,17 +299,17 @@ public class DragAndDropPolicy { */ private static class DefaultStarter implements Starter { private final Context mContext; - private final LegacySplitScreen mLegacySplitScreen; + private final SplitScreen mSplitScreen; - public DefaultStarter(Context context, LegacySplitScreen legacySplitScreen) { + public DefaultStarter(Context context, SplitScreen splitScreen) { mContext = context; - mLegacySplitScreen = legacySplitScreen; + mSplitScreen = splitScreen; } @Override public void startTask(int taskId, Bundle activityOptions) { try { - ActivityTaskManager.getService().startActivityFromRecents(taskId, null); + ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions); } catch (RemoteException e) { Slog.e(TAG, "Failed to launch task", e); } @@ -372,12 +339,14 @@ public class DragAndDropPolicy { @Override public void enterSplitScreen(int taskId, boolean leftOrTop) { - mLegacySplitScreen.splitPrimaryTask(); + mSplitScreen.moveToSideStage(taskId, + leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT + : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT); } @Override - public void exitSplitScreen(int taskId) { - mLegacySplitScreen.dismissSplitToPrimaryTask(); + public void exitSplitScreen() { + mSplitScreen.exitSplitScreen(); } } @@ -406,19 +375,16 @@ public class DragAndDropPolicy { final Rect hitRegion; // The approximate visual region for where the task will start final Rect drawRegion; - // The - final Rect dropTargetBounds; - public Target(@Type int t, Rect hit, Rect draw, Rect drop) { + public Target(@Type int t, Rect hit, Rect draw) { type = t; hitRegion = hit; drawRegion = draw; - dropTargetBounds = drop; } @Override public String toString() { - return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}"; + return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}"; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index a56fe8ded2e3..82c4e440fb15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -42,7 +42,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen; import java.util.ArrayList; @@ -61,7 +61,7 @@ public class DragLayout extends View { private boolean mIsShowing; private boolean mHasDropped; - public DragLayout(Context context, LegacySplitScreen splitscreen) { + public DragLayout(Context context, SplitScreen splitscreen) { super(context); mPolicy = new DragAndDropPolicy(context, splitscreen); mDisplayMargin = context.getResources().getDimensionPixelSize( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index 552eba4ed5b3..7f98965d7f1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -38,12 +38,6 @@ class MainStage extends StageTaskListener { private boolean mIsActive = false; - private static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; - private static final int[] CONTROLLED_WINDOWING_MODES = - {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; - private static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = - {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; - MainStage(ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) { super(taskOrganizer, displayId, callbacks, syncQueue); @@ -92,7 +86,7 @@ class MainStage extends StageTaskListener { null /* newParent */, CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, CONTROLLED_ACTIVITY_TYPES, - true /* onTop */) + false /* onTop */) .reorder(rootToken, false /* onTop */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 5645c19d5c46..cd44e4b7cebf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -16,6 +16,8 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import android.app.ActivityManager; import android.graphics.Rect; import android.window.WindowContainerToken; @@ -48,6 +50,17 @@ class SideStage extends StageTaskListener { .reorder(rootToken, true); } + boolean removeAllTasks(WindowContainerTransaction wct) { + if (mChildrenTaskInfo.size() == 0) return false; + wct.reparentTasks( + mRootTaskInfo.token, + null /* newParent */, + CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, + CONTROLLED_ACTIVITY_TYPES, + false /* onTop */); + return true; + } + boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); if (task == null) return false; @@ -57,4 +70,12 @@ class SideStage extends StageTaskListener { .reparent(task.token, newParent, false /* onTop */); return true; } + + int getTopVisibleTaskId() { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.valueAt(i); + if (task.isVisible) return task.taskId; + } + return INVALID_TASK_ID; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 08c2856d6792..7c1b9d813851 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,6 +18,8 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.app.ActivityManager; +import android.graphics.Rect; +import android.os.Bundle; import androidx.annotation.NonNull; @@ -61,6 +63,12 @@ public interface SplitScreen { void setSideStagePosition(@SideStagePosition int sideStagePosition); /** Hides the side-stage if it is currently visible. */ void setSideStageVisibility(boolean visible); + /** Removes the split-screen stages. */ + void exitSplitScreen(); + /** Gets the stage bounds. */ + void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds); + /** Updates the launch activity options for the split position we want to launch it in. */ + void updateActivityOptions(Bundle opts, @SideStagePosition int position); /** Dumps current status of split-screen. */ void dump(@NonNull PrintWriter pw, String prefix); /** Called when the shell organizer has been registered. */ 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 55cfea5b6da3..27d3b81d41b5 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 @@ -20,6 +20,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import android.app.ActivityManager; import android.content.Context; +import android.graphics.Rect; +import android.os.Bundle; import androidx.annotation.NonNull; @@ -69,7 +71,10 @@ public class SplitScreenController implements SplitScreen { @Override public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); - return task != null && moveToSideStage(task, sideStagePosition); + if (task == null) { + throw new IllegalArgumentException("Unknown taskId" + taskId); + } + return moveToSideStage(task, sideStagePosition); } @Override @@ -94,6 +99,21 @@ public class SplitScreenController implements SplitScreen { } @Override + public void exitSplitScreen() { + mStageCoordinator.exitSplitScreen(); + } + + @Override + public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { + mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds); + } + + @Override + public void updateActivityOptions(Bundle opts, @SideStagePosition int position) { + mStageCoordinator.updateActivityOptions(opts, position); + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { pw.println(prefix + TAG); if (mStageCoordinator != null) { 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 5c1d18e71800..dd41957935d7 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 @@ -16,6 +16,8 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -25,6 +27,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_T import android.app.ActivityManager; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.view.SurfaceControl; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; @@ -142,6 +145,31 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mTaskOrganizer.applyTransaction(wct); } + void exitSplitScreen() { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStage.removeAllTasks(wct); + mMainStage.deactivate(wct); + mTaskOrganizer.applyTransaction(wct); + } + + void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { + outTopOrLeftBounds.set(mSplitLayout.getBounds1()); + outBottomOrRightBounds.set(mSplitLayout.getBounds2()); + } + + void updateActivityOptions(Bundle opts, @SplitScreen.SideStagePosition int position) { + final StageTaskListener stage = position == mSideStagePosition ? mSideStage : mMainStage; + opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); + + if (!mMainStage.isActive()) { + // Activate the main stage in anticipation of an app launch. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mMainStage.activate(getMainStageBounds(), wct); + mSideStage.setBounds(getSideStageBounds(), wct); + mTaskOrganizer.applyTransaction(wct); + } + } + private void onStageRootTaskAppeared(StageListenerImpl stageListener) { if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) { final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -175,6 +203,12 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } } + if (!mainStageVisible && !sideStageVisible) { + // Exit split-screen if both stage are not visible. + // TODO: This is only a temporary request from UX and is likely to be removed soon... + exitSplitScreen(); + } + if (mainStageVisible) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (sideStageVisible) { @@ -252,10 +286,27 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } @Override - public void onSnappedToDismiss(boolean snappedToEnd) { - // TODO: What to do...what to do... + public void onSnappedToDismiss(boolean bottomOrRight) { + if (mSideStagePosition == SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT && bottomOrRight) { + // Main stage was fully expanded...Just side side-stage. + setSideStageVisibility(false); + } else { + // Side stage was fully expanded...Move its top task to the main stage + // and hide side-stage. + // TODO: Would UX prefer the side-stage go into fullscreen mode here? + final int taskId = mSideStage.getTopVisibleTaskId(); + if (taskId == INVALID_TASK_ID) { + throw new IllegalStateException("Side stage doesn't have visible task? " + + mSideStage); + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStage.removeTask(taskId, mMainStage.mRootTaskInfo.getToken(), wct); + mSideStage.setVisibility(false, wct); + mTaskOrganizer.applyTransaction(wct); + } + + // Reset divider position. mSplitLayout.resetDividerPosition(); - onBoundsChanged(mSplitLayout); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index efd42ce552b1..1aa7552c01eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -16,7 +16,10 @@ package com.android.wm.shell.splitscreen; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.annotation.CallSuper; import android.app.ActivityManager; @@ -45,6 +48,12 @@ import java.io.PrintWriter; class StageTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = StageTaskListener.class.getSimpleName(); + protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; + protected static final int[] CONTROLLED_WINDOWING_MODES = + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; + protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; + /** Callback interface for listening to changes in a split-screen stage. */ public interface StageListenerCallbacks { void onRootTaskAppeared(); @@ -69,7 +78,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (!taskInfo.hasParentTask()) { + if (mRootTaskInfo == null && !taskInfo.hasParentTask()) { mRootLeash = leash; mRootTaskInfo = taskInfo; mCallbacks.onRootTaskAppeared(); @@ -78,7 +87,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); } else { - throw new IllegalArgumentException("Unknown task: " + taskInfo); + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); } sendStatusChanged(); } @@ -93,7 +103,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { updateChildTaskSurface( taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); } else { - throw new IllegalArgumentException("Unknown task: " + taskInfo); + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); } sendStatusChanged(); } @@ -110,7 +121,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenLeashes.remove(taskId); sendStatusChanged(); } else { - throw new IllegalArgumentException("Unknown task: " + taskInfo); + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); } } @@ -143,6 +155,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void dump(@NonNull PrintWriter pw, String prefix) { - + final String innerPrefix = prefix + " "; + final String childPrefix = innerPrefix + " "; + pw.println(prefix + this); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 912418d0eebd..79bdaf43f171 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -26,7 +26,9 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; +import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; +import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -35,7 +37,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -52,7 +53,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; -import android.graphics.Rect; import android.os.RemoteException; import android.view.DisplayInfo; @@ -61,20 +61,17 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; -import com.android.wm.shell.legacysplitscreen.DividerView; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.function.Consumer; /** * Tests for the drag and drop policy. @@ -90,12 +87,13 @@ public class DragAndDropPolicyTest { private ActivityTaskManager mActivityTaskManager; @Mock - private LegacySplitScreen mLegacySplitScreen; + private SplitScreen mSplitScreen; @Mock private DragAndDropPolicy.Starter mStarter; - private DisplayLayout mDisplayLayout; + private DisplayLayout mLandscapeDisplayLayout; + private DisplayLayout mPortraitDisplayLayout; private Insets mInsets; private DragAndDropPolicy mPolicy; @@ -116,25 +114,19 @@ public class DragAndDropPolicyTest { Resources res = mock(Resources.class); Configuration config = new Configuration(); doReturn(config).when(res).getConfiguration(); + doReturn(res).when(mContext).getResources(); DisplayInfo info = new DisplayInfo(); - info.logicalWidth = 100; + info.logicalWidth = 200; info.logicalHeight = 100; - mDisplayLayout = new DisplayLayout(info, res, false, false); + mLandscapeDisplayLayout = new DisplayLayout(info, res, false, false); + DisplayInfo info2 = new DisplayInfo(); + info.logicalWidth = 100; + info.logicalHeight = 200; + mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false); mInsets = Insets.of(0, 0, 0, 0); - DividerView divider = mock(DividerView.class); - doReturn(divider).when(mLegacySplitScreen).getDividerView(); - doReturn(new Rect(50, 0, 100, 100)).when(divider) - .getNonMinimizedSplitScreenSecondaryBounds(); - - doAnswer((Answer<Void>) invocation -> { - Consumer<Boolean> callback = invocation.getArgument(0); - callback.accept(true); - return null; - }).when(mLegacySplitScreen).registerInSplitScreenListener(any()); - mPolicy = new DragAndDropPolicy( - mContext, mActivityTaskManager, mLegacySplitScreen, mStarter); + mContext, mActivityTaskManager, mSplitScreen, mStarter); mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); setClipDataResizeable(mNonResizeableActivityClipData, false); @@ -149,7 +141,6 @@ public class DragAndDropPolicyTest { mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); - setIsPhone(false); setInSplitScreen(false); setRunningTask(mFullscreenAppTask); } @@ -199,22 +190,14 @@ public class DragAndDropPolicyTest { : ActivityInfo.RESIZE_MODE_UNRESIZEABLE; } - private void setIsPhone(boolean isPhone) { - Resources res = mock(Resources.class); - Configuration config = mock(Configuration.class); - config.smallestScreenWidthDp = isPhone ? 400 : 800; - doReturn(config).when(res).getConfiguration(); - doReturn(res).when(mContext).getResources(); - } - private void setInSplitScreen(boolean inSplitscreen) { - doReturn(inSplitscreen).when(mLegacySplitScreen).isDividerVisible(); + doReturn(inSplitscreen).when(mSplitScreen).isSplitScreenVisible(); } @Test - public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() throws RemoteException { + public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { setRunningTask(mHomeTask); - mPolicy.start(mDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); @@ -223,77 +206,66 @@ public class DragAndDropPolicyTest { } @Test - public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() - throws RemoteException { + public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); - // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split - // pairs + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); + verify(mStarter).exitSplitScreen(); verify(mStarter).startIntent(any(), any()); reset(mStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mStarter).enterSplitScreen(anyInt(), eq(false)); verify(mStarter).startIntent(any(), any()); } @Test - public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() - throws RemoteException { - setIsPhone(true); + public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); - // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split - // pairs + mPolicy.start(mPortraitDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); + verify(mStarter).exitSplitScreen(); verify(mStarter).startIntent(any(), any()); reset(mStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mStarter).enterSplitScreen(anyInt(), eq(false)); verify(mStarter).startIntent(any(), any()); } @Test - public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() - throws RemoteException { + public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() { setRunningTask(mNonResizeableFullscreenAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); verify(mStarter).startIntent(any(), any()); } @Test - public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() - throws RemoteException { + public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mDisplayLayout, mNonResizeableActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mNonResizeableActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); verify(mStarter).startIntent(any(), any()); } @Test - public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() throws RemoteException { + public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() { setInSplitScreen(true); setRunningTask(mSplitPrimaryAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); - // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split - // pairs + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); verify(mStarter).startIntent(any(), any()); @@ -305,16 +277,12 @@ public class DragAndDropPolicyTest { } @Test - public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() - throws RemoteException { - setIsPhone(true); + public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() { setInSplitScreen(true); setRunningTask(mSplitPrimaryAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); - // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split - // pairs + mPolicy.start(mPortraitDisplayLayout, mActivityClipData); ArrayList<Target> targets = assertExactTargetTypes( - mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM); + mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); verify(mStarter).startIntent(any(), any()); @@ -326,9 +294,9 @@ public class DragAndDropPolicyTest { } @Test - public void testTargetHitRects() throws RemoteException { + public void testTargetHitRects() { setRunningTask(mFullscreenAppTask); - mPolicy.start(mDisplayLayout, mActivityClipData); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t); diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp index 6c6c5c9f4e22..8a10599b498f 100644 --- a/libs/androidfw/LocaleDataTables.cpp +++ b/libs/androidfw/LocaleDataTables.cpp @@ -64,42 +64,43 @@ const char SCRIPT_CODES[][4] = { /* 60 */ {'N', 'k', 'o', 'o'}, /* 61 */ {'N', 's', 'h', 'u'}, /* 62 */ {'O', 'g', 'a', 'm'}, - /* 63 */ {'O', 'r', 'k', 'h'}, - /* 64 */ {'O', 'r', 'y', 'a'}, - /* 65 */ {'O', 's', 'g', 'e'}, - /* 66 */ {'P', 'a', 'u', 'c'}, - /* 67 */ {'P', 'h', 'l', 'i'}, - /* 68 */ {'P', 'h', 'n', 'x'}, - /* 69 */ {'P', 'l', 'r', 'd'}, - /* 70 */ {'P', 'r', 't', 'i'}, - /* 71 */ {'R', 'u', 'n', 'r'}, - /* 72 */ {'S', 'a', 'm', 'r'}, - /* 73 */ {'S', 'a', 'r', 'b'}, - /* 74 */ {'S', 'a', 'u', 'r'}, - /* 75 */ {'S', 'g', 'n', 'w'}, - /* 76 */ {'S', 'i', 'n', 'h'}, - /* 77 */ {'S', 'o', 'g', 'd'}, - /* 78 */ {'S', 'o', 'r', 'a'}, - /* 79 */ {'S', 'o', 'y', 'o'}, - /* 80 */ {'S', 'y', 'r', 'c'}, - /* 81 */ {'T', 'a', 'l', 'e'}, - /* 82 */ {'T', 'a', 'l', 'u'}, - /* 83 */ {'T', 'a', 'm', 'l'}, - /* 84 */ {'T', 'a', 'n', 'g'}, - /* 85 */ {'T', 'a', 'v', 't'}, - /* 86 */ {'T', 'e', 'l', 'u'}, - /* 87 */ {'T', 'f', 'n', 'g'}, - /* 88 */ {'T', 'h', 'a', 'a'}, - /* 89 */ {'T', 'h', 'a', 'i'}, - /* 90 */ {'T', 'i', 'b', 't'}, - /* 91 */ {'U', 'g', 'a', 'r'}, - /* 92 */ {'V', 'a', 'i', 'i'}, - /* 93 */ {'W', 'c', 'h', 'o'}, - /* 94 */ {'X', 'p', 'e', 'o'}, - /* 95 */ {'X', 's', 'u', 'x'}, - /* 96 */ {'Y', 'i', 'i', 'i'}, - /* 97 */ {'~', '~', '~', 'A'}, - /* 98 */ {'~', '~', '~', 'B'}, + /* 63 */ {'O', 'l', 'c', 'k'}, + /* 64 */ {'O', 'r', 'k', 'h'}, + /* 65 */ {'O', 'r', 'y', 'a'}, + /* 66 */ {'O', 's', 'g', 'e'}, + /* 67 */ {'P', 'a', 'u', 'c'}, + /* 68 */ {'P', 'h', 'l', 'i'}, + /* 69 */ {'P', 'h', 'n', 'x'}, + /* 70 */ {'P', 'l', 'r', 'd'}, + /* 71 */ {'P', 'r', 't', 'i'}, + /* 72 */ {'R', 'u', 'n', 'r'}, + /* 73 */ {'S', 'a', 'm', 'r'}, + /* 74 */ {'S', 'a', 'r', 'b'}, + /* 75 */ {'S', 'a', 'u', 'r'}, + /* 76 */ {'S', 'g', 'n', 'w'}, + /* 77 */ {'S', 'i', 'n', 'h'}, + /* 78 */ {'S', 'o', 'g', 'd'}, + /* 79 */ {'S', 'o', 'r', 'a'}, + /* 80 */ {'S', 'o', 'y', 'o'}, + /* 81 */ {'S', 'y', 'r', 'c'}, + /* 82 */ {'T', 'a', 'l', 'e'}, + /* 83 */ {'T', 'a', 'l', 'u'}, + /* 84 */ {'T', 'a', 'm', 'l'}, + /* 85 */ {'T', 'a', 'n', 'g'}, + /* 86 */ {'T', 'a', 'v', 't'}, + /* 87 */ {'T', 'e', 'l', 'u'}, + /* 88 */ {'T', 'f', 'n', 'g'}, + /* 89 */ {'T', 'h', 'a', 'a'}, + /* 90 */ {'T', 'h', 'a', 'i'}, + /* 91 */ {'T', 'i', 'b', 't'}, + /* 92 */ {'U', 'g', 'a', 'r'}, + /* 93 */ {'V', 'a', 'i', 'i'}, + /* 94 */ {'W', 'c', 'h', 'o'}, + /* 95 */ {'X', 'p', 'e', 'o'}, + /* 96 */ {'X', 's', 'u', 'x'}, + /* 97 */ {'Y', 'i', 'i', 'i'}, + /* 98 */ {'~', '~', '~', 'A'}, + /* 99 */ {'~', '~', '~', 'B'}, }; @@ -120,7 +121,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x80600000u, 46u}, // ada -> Latn {0x90600000u, 46u}, // ade -> Latn {0xA4600000u, 46u}, // adj -> Latn - {0xBC600000u, 90u}, // adp -> Tibt + {0xBC600000u, 91u}, // adp -> Tibt {0xE0600000u, 17u}, // ady -> Cyrl {0xE4600000u, 46u}, // adz -> Latn {0x61650000u, 4u}, // ae -> Avst @@ -138,7 +139,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB8E00000u, 0u}, // aho -> Ahom {0x99200000u, 46u}, // ajg -> Latn {0x616B0000u, 46u}, // ak -> Latn - {0xA9400000u, 95u}, // akk -> Xsux + {0xA9400000u, 96u}, // akk -> Xsux {0x81600000u, 46u}, // ala -> Latn {0xA1600000u, 46u}, // ali -> Latn {0xB5600000u, 46u}, // aln -> Latn @@ -163,7 +164,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC9E00000u, 46u}, // aps -> Latn {0xE5E00000u, 46u}, // apz -> Latn {0x61720000u, 1u}, // ar -> Arab - {0x61725842u, 98u}, // ar-XB -> ~~~B + {0x61725842u, 99u}, // ar-XB -> ~~~B {0x8A200000u, 2u}, // arc -> Armi {0x9E200000u, 46u}, // arh -> Latn {0xB6200000u, 46u}, // arn -> Latn @@ -174,7 +175,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE6200000u, 1u}, // arz -> Arab {0x61730000u, 7u}, // as -> Beng {0x82400000u, 46u}, // asa -> Latn - {0x92400000u, 75u}, // ase -> Sgnw + {0x92400000u, 76u}, // ase -> Sgnw {0x9A400000u, 46u}, // asg -> Latn {0xBA400000u, 46u}, // aso -> Latn {0xCE400000u, 46u}, // ast -> Latn @@ -231,7 +232,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xDC810000u, 46u}, // bex -> Latn {0xE4810000u, 46u}, // bez -> Latn {0x8CA10000u, 46u}, // bfd -> Latn - {0xC0A10000u, 83u}, // bfq -> Taml + {0xC0A10000u, 84u}, // bfq -> Taml {0xCCA10000u, 1u}, // bft -> Arab {0xE0A10000u, 18u}, // bfy -> Deva {0x62670000u, 17u}, // bg -> Cyrl @@ -265,7 +266,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC1410000u, 46u}, // bkq -> Latn {0xD1410000u, 46u}, // bku -> Latn {0xD5410000u, 46u}, // bkv -> Latn - {0xCD610000u, 85u}, // blt -> Tavt + {0xCD610000u, 86u}, // blt -> Tavt {0x626D0000u, 46u}, // bm -> Latn {0x9D810000u, 46u}, // bmh -> Latn {0xA9810000u, 46u}, // bmk -> Latn @@ -275,7 +276,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x99A10000u, 46u}, // bng -> Latn {0xB1A10000u, 46u}, // bnm -> Latn {0xBDA10000u, 46u}, // bnp -> Latn - {0x626F0000u, 90u}, // bo -> Tibt + {0x626F0000u, 91u}, // bo -> Tibt {0xA5C10000u, 46u}, // boj -> Latn {0xB1C10000u, 46u}, // bom -> Latn {0xB5C10000u, 46u}, // bon -> Latn @@ -322,6 +323,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9F210000u, 46u}, // bzh -> Latn {0xDB210000u, 46u}, // bzw -> Latn {0x63610000u, 46u}, // ca -> Latn + {0x8C020000u, 46u}, // cad -> Latn {0xB4020000u, 46u}, // can -> Latn {0xA4220000u, 46u}, // cbj -> Latn {0x9C420000u, 46u}, // cch -> Latn @@ -346,7 +348,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE1420000u, 46u}, // cky -> Latn {0x81620000u, 46u}, // cla -> Latn {0x91820000u, 46u}, // cme -> Latn - {0x99820000u, 79u}, // cmg -> Soyo + {0x99820000u, 80u}, // cmg -> Soyo {0x636F0000u, 46u}, // co -> Latn {0xBDC20000u, 15u}, // cop -> Copt {0xC9E20000u, 46u}, // cps -> Latn @@ -360,7 +362,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x63730000u, 46u}, // cs -> Latn {0x86420000u, 46u}, // csb -> Latn {0xDA420000u, 10u}, // csw -> Cans - {0x8E620000u, 66u}, // ctd -> Pauc + {0x8E620000u, 67u}, // ctd -> Pauc {0x63750000u, 17u}, // cu -> Cyrl {0x63760000u, 17u}, // cv -> Cyrl {0x63790000u, 46u}, // cy -> Latn @@ -389,7 +391,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x91230000u, 46u}, // dje -> Latn {0xA5A30000u, 46u}, // dnj -> Latn {0x85C30000u, 46u}, // dob -> Latn - {0xA1C30000u, 1u}, // doi -> Arab + {0xA1C30000u, 18u}, // doi -> Deva {0xBDC30000u, 46u}, // dop -> Latn {0xD9C30000u, 46u}, // dow -> Latn {0x9E230000u, 56u}, // drh -> Mong @@ -404,12 +406,12 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x8A830000u, 46u}, // duc -> Latn {0x8E830000u, 46u}, // dud -> Latn {0x9A830000u, 46u}, // dug -> Latn - {0x64760000u, 88u}, // dv -> Thaa + {0x64760000u, 89u}, // dv -> Thaa {0x82A30000u, 46u}, // dva -> Latn {0xDAC30000u, 46u}, // dww -> Latn {0xBB030000u, 46u}, // dyo -> Latn {0xD3030000u, 46u}, // dyu -> Latn - {0x647A0000u, 90u}, // dz -> Tibt + {0x647A0000u, 91u}, // dz -> Tibt {0x9B230000u, 46u}, // dzg -> Latn {0xD0240000u, 46u}, // ebu -> Latn {0x65650000u, 46u}, // ee -> Latn @@ -422,7 +424,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x81840000u, 46u}, // ema -> Latn {0xA1840000u, 46u}, // emi -> Latn {0x656E0000u, 46u}, // en -> Latn - {0x656E5841u, 97u}, // en-XA -> ~~~A + {0x656E5841u, 98u}, // en-XA -> ~~~A {0xB5A40000u, 46u}, // enn -> Latn {0xC1A40000u, 46u}, // enq -> Latn {0x656F0000u, 46u}, // eo -> Latn @@ -438,6 +440,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x65750000u, 46u}, // eu -> Latn {0xBAC40000u, 46u}, // ewo -> Latn {0xCEE40000u, 46u}, // ext -> Latn + {0x83240000u, 46u}, // eza -> Latn {0x66610000u, 1u}, // fa -> Arab {0x80050000u, 46u}, // faa -> Latn {0x84050000u, 46u}, // fab -> Latn @@ -521,7 +524,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x95C60000u, 20u}, // gof -> Ethi {0xA1C60000u, 46u}, // goi -> Latn {0xB1C60000u, 18u}, // gom -> Deva - {0xB5C60000u, 86u}, // gon -> Telu + {0xB5C60000u, 87u}, // gon -> Telu {0xC5C60000u, 46u}, // gor -> Latn {0xC9C60000u, 46u}, // gos -> Latn {0xCDC60000u, 24u}, // got -> Goth @@ -566,7 +569,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xAD070000u, 46u}, // hil -> Latn {0x81670000u, 46u}, // hla -> Latn {0xD1670000u, 32u}, // hlu -> Hluw - {0x8D870000u, 69u}, // hmd -> Plrd + {0x8D870000u, 70u}, // hmd -> Plrd {0xCD870000u, 46u}, // hmt -> Latn {0x8DA70000u, 1u}, // hnd -> Arab {0x91A70000u, 18u}, // hne -> Deva @@ -601,7 +604,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x69670000u, 46u}, // ig -> Latn {0x84C80000u, 46u}, // igb -> Latn {0x90C80000u, 46u}, // ige -> Latn - {0x69690000u, 96u}, // ii -> Yiii + {0x69690000u, 97u}, // ii -> Yiii {0xA5280000u, 46u}, // ijj -> Latn {0x696B0000u, 46u}, // ik -> Latn {0xA9480000u, 46u}, // ikk -> Latn @@ -626,6 +629,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x6A610000u, 36u}, // ja -> Jpan {0x84090000u, 46u}, // jab -> Latn {0xB0090000u, 46u}, // jam -> Latn + {0xC4090000u, 46u}, // jar -> Latn {0xB8290000u, 46u}, // jbo -> Latn {0xD0290000u, 46u}, // jbu -> Latn {0xB4890000u, 46u}, // jen -> Latn @@ -661,7 +665,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x906A0000u, 46u}, // kde -> Latn {0x9C6A0000u, 1u}, // kdh -> Arab {0xAC6A0000u, 46u}, // kdl -> Latn - {0xCC6A0000u, 89u}, // kdt -> Thai + {0xCC6A0000u, 90u}, // kdt -> Thai {0x808A0000u, 46u}, // kea -> Latn {0xB48A0000u, 46u}, // ken -> Latn {0xE48A0000u, 46u}, // kez -> Latn @@ -673,7 +677,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x94CA0000u, 46u}, // kgf -> Latn {0xBCCA0000u, 46u}, // kgp -> Latn {0x80EA0000u, 46u}, // kha -> Latn - {0x84EA0000u, 82u}, // khb -> Talu + {0x84EA0000u, 83u}, // khb -> Talu {0xB4EA0000u, 18u}, // khn -> Deva {0xC0EA0000u, 46u}, // khq -> Latn {0xC8EA0000u, 46u}, // khs -> Latn @@ -766,7 +770,8 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x82EA0000u, 46u}, // kxa -> Latn {0x8AEA0000u, 20u}, // kxc -> Ethi {0x92EA0000u, 46u}, // kxe -> Latn - {0xB2EA0000u, 89u}, // kxm -> Thai + {0xAEEA0000u, 18u}, // kxl -> Deva + {0xB2EA0000u, 90u}, // kxm -> Thai {0xBEEA0000u, 1u}, // kxp -> Arab {0xDAEA0000u, 46u}, // kxw -> Latn {0xE6EA0000u, 46u}, // kxz -> Latn @@ -775,6 +780,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x6B795452u, 46u}, // ky-TR -> Latn {0x930A0000u, 46u}, // kye -> Latn {0xDF0A0000u, 46u}, // kyx -> Latn + {0x9F2A0000u, 1u}, // kzh -> Arab {0xA72A0000u, 46u}, // kzj -> Latn {0xC72A0000u, 46u}, // kzr -> Latn {0xCF2A0000u, 46u}, // kzt -> Latn @@ -790,7 +796,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD02B0000u, 46u}, // lbu -> Latn {0xD82B0000u, 46u}, // lbw -> Latn {0xB04B0000u, 46u}, // lcm -> Latn - {0xBC4B0000u, 89u}, // lcp -> Thai + {0xBC4B0000u, 90u}, // lcp -> Thai {0x846B0000u, 46u}, // ldb -> Latn {0x8C8B0000u, 46u}, // led -> Latn {0x908B0000u, 46u}, // lee -> Latn @@ -814,7 +820,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xCD4B0000u, 46u}, // lkt -> Latn {0x916B0000u, 46u}, // lle -> Latn {0xB56B0000u, 46u}, // lln -> Latn - {0xB58B0000u, 86u}, // lmn -> Telu + {0xB58B0000u, 87u}, // lmn -> Telu {0xB98B0000u, 46u}, // lmo -> Latn {0xBD8B0000u, 46u}, // lmp -> Latn {0x6C6E0000u, 46u}, // ln -> Latn @@ -836,7 +842,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE28B0000u, 46u}, // luy -> Latn {0xE68B0000u, 1u}, // luz -> Arab {0x6C760000u, 46u}, // lv -> Latn - {0xAECB0000u, 89u}, // lwl -> Thai + {0xAECB0000u, 90u}, // lwl -> Thai {0x9F2B0000u, 28u}, // lzh -> Hans {0xE72B0000u, 46u}, // lzz -> Latn {0x8C0C0000u, 46u}, // mad -> Latn @@ -927,7 +933,6 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xBA2C0000u, 57u}, // mro -> Mroo {0x6D730000u, 46u}, // ms -> Latn {0x6D734343u, 1u}, // ms-CC -> Arab - {0x6D734944u, 1u}, // ms-ID -> Arab {0x6D740000u, 46u}, // mt -> Latn {0x8A6C0000u, 46u}, // mtc -> Latn {0x966C0000u, 46u}, // mtf -> Latn @@ -1006,11 +1011,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9DAD0000u, 46u}, // nnh -> Latn {0xA9AD0000u, 46u}, // nnk -> Latn {0xB1AD0000u, 46u}, // nnm -> Latn - {0xBDAD0000u, 93u}, // nnp -> Wcho + {0xBDAD0000u, 94u}, // nnp -> Wcho {0x6E6F0000u, 46u}, // no -> Latn {0x8DCD0000u, 44u}, // nod -> Lana {0x91CD0000u, 18u}, // noe -> Deva - {0xB5CD0000u, 71u}, // non -> Runr + {0xB5CD0000u, 72u}, // non -> Runr {0xBDCD0000u, 46u}, // nop -> Latn {0xD1CD0000u, 46u}, // nou -> Latn {0xBA0D0000u, 60u}, // nqo -> Nkoo @@ -1044,18 +1049,18 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB5AE0000u, 46u}, // onn -> Latn {0xC9AE0000u, 46u}, // ons -> Latn {0xB1EE0000u, 46u}, // opm -> Latn - {0x6F720000u, 64u}, // or -> Orya + {0x6F720000u, 65u}, // or -> Orya {0xBA2E0000u, 46u}, // oro -> Latn {0xD22E0000u, 1u}, // oru -> Arab {0x6F730000u, 17u}, // os -> Cyrl - {0x824E0000u, 65u}, // osa -> Osge + {0x824E0000u, 66u}, // osa -> Osge {0x826E0000u, 1u}, // ota -> Arab - {0xAA6E0000u, 63u}, // otk -> Orkh + {0xAA6E0000u, 64u}, // otk -> Orkh {0xB32E0000u, 46u}, // ozm -> Latn {0x70610000u, 27u}, // pa -> Guru {0x7061504Bu, 1u}, // pa-PK -> Arab {0x980F0000u, 46u}, // pag -> Latn - {0xAC0F0000u, 67u}, // pal -> Phli + {0xAC0F0000u, 68u}, // pal -> Phli {0xB00F0000u, 46u}, // pam -> Latn {0xBC0F0000u, 46u}, // pap -> Latn {0xD00F0000u, 46u}, // pau -> Latn @@ -1065,11 +1070,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x886F0000u, 46u}, // pdc -> Latn {0xCC6F0000u, 46u}, // pdt -> Latn {0x8C8F0000u, 46u}, // ped -> Latn - {0xB88F0000u, 94u}, // peo -> Xpeo + {0xB88F0000u, 95u}, // peo -> Xpeo {0xDC8F0000u, 46u}, // pex -> Latn {0xACAF0000u, 46u}, // pfl -> Latn {0xACEF0000u, 1u}, // phl -> Arab - {0xB4EF0000u, 68u}, // phn -> Phnx + {0xB4EF0000u, 69u}, // phn -> Phnx {0xAD0F0000u, 46u}, // pil -> Latn {0xBD0F0000u, 46u}, // pip -> Latn {0x814F0000u, 8u}, // pka -> Brah @@ -1105,7 +1110,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB4D10000u, 46u}, // rgn -> Latn {0x98F10000u, 1u}, // rhg -> Arab {0x81110000u, 46u}, // ria -> Latn - {0x95110000u, 87u}, // rif -> Tfng + {0x95110000u, 88u}, // rif -> Tfng {0x95114E4Cu, 46u}, // rif-NL -> Latn {0xC9310000u, 18u}, // rjs -> Deva {0xCD510000u, 7u}, // rkt -> Beng @@ -1135,9 +1140,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9C120000u, 17u}, // sah -> Cyrl {0xC0120000u, 46u}, // saq -> Latn {0xC8120000u, 46u}, // sas -> Latn - {0xCC120000u, 46u}, // sat -> Latn + {0xCC120000u, 63u}, // sat -> Olck {0xD4120000u, 46u}, // sav -> Latn - {0xE4120000u, 74u}, // saz -> Saur + {0xE4120000u, 75u}, // saz -> Saur {0x80320000u, 46u}, // sba -> Latn {0x90320000u, 46u}, // sbe -> Latn {0xBC320000u, 46u}, // sbp -> Latn @@ -1161,11 +1166,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD8D20000u, 20u}, // sgw -> Ethi {0xE4D20000u, 46u}, // sgz -> Latn {0x73680000u, 46u}, // sh -> Latn - {0xA0F20000u, 87u}, // shi -> Tfng + {0xA0F20000u, 88u}, // shi -> Tfng {0xA8F20000u, 46u}, // shk -> Latn {0xB4F20000u, 58u}, // shn -> Mymr {0xD0F20000u, 1u}, // shu -> Arab - {0x73690000u, 76u}, // si -> Sinh + {0x73690000u, 77u}, // si -> Sinh {0x8D120000u, 46u}, // sid -> Latn {0x99120000u, 46u}, // sig -> Latn {0xAD120000u, 46u}, // sil -> Latn @@ -1184,7 +1189,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x81920000u, 46u}, // sma -> Latn {0xA5920000u, 46u}, // smj -> Latn {0xB5920000u, 46u}, // smn -> Latn - {0xBD920000u, 72u}, // smp -> Samr + {0xBD920000u, 73u}, // smp -> Samr {0xC1920000u, 46u}, // smq -> Latn {0xC9920000u, 46u}, // sms -> Latn {0x736E0000u, 46u}, // sn -> Latn @@ -1194,10 +1199,10 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xDDB20000u, 46u}, // snx -> Latn {0xE1B20000u, 46u}, // sny -> Latn {0x736F0000u, 46u}, // so -> Latn - {0x99D20000u, 77u}, // sog -> Sogd + {0x99D20000u, 78u}, // sog -> Sogd {0xA9D20000u, 46u}, // sok -> Latn {0xC1D20000u, 46u}, // soq -> Latn - {0xD1D20000u, 89u}, // sou -> Thai + {0xD1D20000u, 90u}, // sou -> Thai {0xE1D20000u, 46u}, // soy -> Latn {0x8DF20000u, 46u}, // spd -> Latn {0xADF20000u, 46u}, // spl -> Latn @@ -1208,7 +1213,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x7372524Fu, 46u}, // sr-RO -> Latn {0x73725255u, 46u}, // sr-RU -> Latn {0x73725452u, 46u}, // sr-TR -> Latn - {0x86320000u, 78u}, // srb -> Sora + {0x86320000u, 79u}, // srb -> Sora {0xB6320000u, 46u}, // srn -> Latn {0xC6320000u, 46u}, // srr -> Latn {0xDE320000u, 18u}, // srx -> Deva @@ -1235,9 +1240,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB6F20000u, 46u}, // sxn -> Latn {0xDAF20000u, 46u}, // sxw -> Latn {0xAF120000u, 7u}, // syl -> Beng - {0xC7120000u, 80u}, // syr -> Syrc + {0xC7120000u, 81u}, // syr -> Syrc {0xAF320000u, 46u}, // szl -> Latn - {0x74610000u, 83u}, // ta -> Taml + {0x74610000u, 84u}, // ta -> Taml {0xA4130000u, 18u}, // taj -> Deva {0xAC130000u, 46u}, // tal -> Latn {0xB4130000u, 46u}, // tan -> Latn @@ -1251,11 +1256,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE4330000u, 46u}, // tbz -> Latn {0xA0530000u, 46u}, // tci -> Latn {0xE0530000u, 42u}, // tcy -> Knda - {0x8C730000u, 81u}, // tdd -> Tale + {0x8C730000u, 82u}, // tdd -> Tale {0x98730000u, 18u}, // tdg -> Deva {0x9C730000u, 18u}, // tdh -> Deva {0xD0730000u, 46u}, // tdu -> Latn - {0x74650000u, 86u}, // te -> Telu + {0x74650000u, 87u}, // te -> Telu {0x8C930000u, 46u}, // ted -> Latn {0xB0930000u, 46u}, // tem -> Latn {0xB8930000u, 46u}, // teo -> Latn @@ -1266,7 +1271,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x88D30000u, 46u}, // tgc -> Latn {0xB8D30000u, 46u}, // tgo -> Latn {0xD0D30000u, 46u}, // tgu -> Latn - {0x74680000u, 89u}, // th -> Thai + {0x74680000u, 90u}, // th -> Thai {0xACF30000u, 18u}, // thl -> Deva {0xC0F30000u, 18u}, // thq -> Deva {0xC4F30000u, 18u}, // thr -> Deva @@ -1305,14 +1310,14 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x8E530000u, 25u}, // tsd -> Grek {0x96530000u, 18u}, // tsf -> Deva {0x9A530000u, 46u}, // tsg -> Latn - {0xA6530000u, 90u}, // tsj -> Tibt + {0xA6530000u, 91u}, // tsj -> Tibt {0xDA530000u, 46u}, // tsw -> Latn {0x74740000u, 17u}, // tt -> Cyrl {0x8E730000u, 46u}, // ttd -> Latn {0x92730000u, 46u}, // tte -> Latn {0xA6730000u, 46u}, // ttj -> Latn {0xC6730000u, 46u}, // ttr -> Latn - {0xCA730000u, 89u}, // tts -> Thai + {0xCA730000u, 90u}, // tts -> Thai {0xCE730000u, 46u}, // ttt -> Latn {0x9E930000u, 46u}, // tuh -> Latn {0xAE930000u, 46u}, // tul -> Latn @@ -1323,7 +1328,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD2B30000u, 46u}, // tvu -> Latn {0x9ED30000u, 46u}, // twh -> Latn {0xC2D30000u, 46u}, // twq -> Latn - {0x9AF30000u, 84u}, // txg -> Tang + {0x9AF30000u, 85u}, // txg -> Tang {0x74790000u, 46u}, // ty -> Latn {0x83130000u, 46u}, // tya -> Latn {0xD7130000u, 17u}, // tyv -> Cyrl @@ -1333,7 +1338,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x75670000u, 1u}, // ug -> Arab {0x75674B5Au, 17u}, // ug-KZ -> Cyrl {0x75674D4Eu, 17u}, // ug-MN -> Cyrl - {0x80D40000u, 91u}, // uga -> Ugar + {0x80D40000u, 92u}, // uga -> Ugar {0x756B0000u, 17u}, // uk -> Cyrl {0xA1740000u, 46u}, // uli -> Latn {0x85940000u, 46u}, // umb -> Latn @@ -1346,6 +1351,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xCE340000u, 46u}, // urt -> Latn {0xDA340000u, 46u}, // urw -> Latn {0x82540000u, 46u}, // usa -> Latn + {0x9E740000u, 46u}, // uth -> Latn {0xC6740000u, 46u}, // utr -> Latn {0x9EB40000u, 46u}, // uvh -> Latn {0xAEB40000u, 46u}, // uvl -> Latn @@ -1353,7 +1359,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x757A4146u, 1u}, // uz-AF -> Arab {0x757A434Eu, 17u}, // uz-CN -> Cyrl {0x98150000u, 46u}, // vag -> Latn - {0xA0150000u, 92u}, // vai -> Vaii + {0xA0150000u, 93u}, // vai -> Vaii {0xB4150000u, 46u}, // van -> Latn {0x76650000u, 46u}, // ve -> Latn {0x88950000u, 46u}, // vec -> Latn @@ -1376,7 +1382,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB4160000u, 46u}, // wan -> Latn {0xC4160000u, 46u}, // war -> Latn {0xBC360000u, 46u}, // wbp -> Latn - {0xC0360000u, 86u}, // wbq -> Telu + {0xC0360000u, 87u}, // wbq -> Telu {0xC4360000u, 18u}, // wbr -> Deva {0xA0560000u, 46u}, // wci -> Latn {0xC4960000u, 46u}, // wer -> Latn @@ -1418,9 +1424,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC5B70000u, 18u}, // xnr -> Deva {0x99D70000u, 46u}, // xog -> Latn {0xB5D70000u, 46u}, // xon -> Latn - {0xC5F70000u, 70u}, // xpr -> Prti + {0xC5F70000u, 71u}, // xpr -> Prti {0x86370000u, 46u}, // xrb -> Latn - {0x82570000u, 73u}, // xsa -> Sarb + {0x82570000u, 74u}, // xsa -> Sarb {0xA2570000u, 46u}, // xsi -> Latn {0xB2570000u, 46u}, // xsm -> Latn {0xC6570000u, 18u}, // xsr -> Deva @@ -1461,7 +1467,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x98190000u, 46u}, // zag -> Latn {0xA4790000u, 1u}, // zdj -> Arab {0x80990000u, 46u}, // zea -> Latn - {0x9CD90000u, 87u}, // zgh -> Tfng + {0x9CD90000u, 88u}, // zgh -> Tfng {0x7A680000u, 28u}, // zh -> Hans {0x7A684155u, 29u}, // zh-AU -> Hant {0x7A68424Eu, 29u}, // zh-BN -> Hant @@ -1470,7 +1476,6 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x7A68484Bu, 29u}, // zh-HK -> Hant {0x7A684944u, 29u}, // zh-ID -> Hant {0x7A684D4Fu, 29u}, // zh-MO -> Hant - {0x7A684D59u, 29u}, // zh-MY -> Hant {0x7A685041u, 29u}, // zh-PA -> Hant {0x7A685046u, 29u}, // zh-PF -> Hant {0x7A685048u, 29u}, // zh-PH -> Hant @@ -1592,6 +1597,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xD701434D4C61746ELLU, // byv_Latn_CM 0x93214D4C4C61746ELLU, // bze_Latn_ML 0x636145534C61746ELLU, // ca_Latn_ES + 0x8C0255534C61746ELLU, // cad_Latn_US 0x9C424E474C61746ELLU, // cch_Latn_NG 0xBC42424443616B6DLLU, // ccp_Cakm_BD 0x636552554379726CLLU, // ce_Cyrl_RU @@ -1627,6 +1633,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x637652554379726CLLU, // cv_Cyrl_RU 0x637947424C61746ELLU, // cy_Latn_GB 0x6461444B4C61746ELLU, // da_Latn_DK + 0x940343494C61746ELLU, // daf_Latn_CI 0xA80355534C61746ELLU, // dak_Latn_US 0xC40352554379726CLLU, // dar_Cyrl_RU 0xD4034B454C61746ELLU, // dav_Latn_KE @@ -1636,7 +1643,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xC4C343414C61746ELLU, // dgr_Latn_CA 0x91234E454C61746ELLU, // dje_Latn_NE 0xA5A343494C61746ELLU, // dnj_Latn_CI - 0xA1C3494E41726162LLU, // doi_Arab_IN + 0xA1C3494E44657661LLU, // doi_Deva_IN 0x9E23434E4D6F6E67LLU, // drh_Mong_CN 0x864344454C61746ELLU, // dsb_Latn_DE 0xB2634D4C4C61746ELLU, // dtm_Latn_ML @@ -1839,6 +1846,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xC6AA49444C61746ELLU, // kvr_Latn_ID 0xDEAA504B41726162LLU, // kvx_Arab_PK 0x6B7747424C61746ELLU, // kw_Latn_GB + 0xAEEA494E44657661LLU, // kxl_Deva_IN 0xB2EA544854686169LLU, // kxm_Thai_TH 0xBEEA504B41726162LLU, // kxp_Arab_PK 0x6B79434E41726162LLU, // ky_Arab_CN @@ -2047,7 +2055,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x9C1252554379726CLLU, // sah_Cyrl_RU 0xC0124B454C61746ELLU, // saq_Latn_KE 0xC81249444C61746ELLU, // sas_Latn_ID - 0xCC12494E4C61746ELLU, // sat_Latn_IN + 0xCC12494E4F6C636BLLU, // sat_Olck_IN 0xD412534E4C61746ELLU, // sav_Latn_SN 0xE412494E53617572LLU, // saz_Saur_IN 0xBC32545A4C61746ELLU, // sbp_Latn_TZ @@ -2149,6 +2157,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x747254524C61746ELLU, // tr_Latn_TR 0xD23354524C61746ELLU, // tru_Latn_TR 0xD63354574C61746ELLU, // trv_Latn_TW + 0xDA33504B41726162LLU, // trw_Arab_PK 0x74735A414C61746ELLU, // ts_Latn_ZA 0x8E5347524772656BLLU, // tsd_Grek_GR 0x96534E5044657661LLU, // tsf_Deva_NP diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 943423f65882..b944310d8822 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -277,6 +277,28 @@ static jstring FontFileUtil_getFontPostScriptName(JNIEnv* env, jobject, jobject } return env->NewStringUTF(psName->c_str()); } + +static jint FontFileUtil_isPostScriptType1Font(JNIEnv* env, jobject, jobject buffer, jint index) { + NPE_CHECK_RETURN_ZERO(env, buffer); + const void* fontPtr = env->GetDirectBufferAddress(buffer); + if (fontPtr == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer"); + return -1; + } + jlong fontSize = env->GetDirectBufferCapacity(buffer); + if (fontSize <= 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "buffer size must not be zero or negative"); + return -1; + } + minikin::FontFileParser parser(fontPtr, fontSize, index); + std::optional<bool> isType1 = parser.isPostScriptType1Font(); + if (!isType1.has_value()) { + return -1; // not an OpenType font. HarfBuzz failed to parse it. + } + return isType1.value(); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontBuilderMethods[] = { @@ -304,6 +326,8 @@ static const JNINativeMethod gFontFileUtilMethods[] = { { "nGetFontRevision", "(Ljava/nio/ByteBuffer;I)J", (void*) FontFileUtil_getFontRevision }, { "nGetFontPostScriptName", "(Ljava/nio/ByteBuffer;I)Ljava/lang/String;", (void*) FontFileUtil_getFontPostScriptName }, + { "nIsPostScriptType1Font", "(Ljava/nio/ByteBuffer;I)I", + (void*) FontFileUtil_isPostScriptType1Font }, }; int register_android_graphics_fonts_Font(JNIEnv* env) { diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 2703ee35b099..49f9d6612341 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -21,8 +21,10 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.media.metrics.PlaybackComponent; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -35,8 +37,8 @@ import dalvik.system.CloseGuard; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -49,7 +51,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; - /** * MediaDrm can be used to obtain keys for decrypting protected media streams, in * conjunction with {@link android.media.MediaCrypto}. The MediaDrm APIs @@ -963,14 +964,30 @@ public final class MediaDrm implements AutoCloseable { * a session */ @NonNull - public native byte[] openSession(@SecurityLevel int level) throws + public byte[] openSession(@SecurityLevel int level) throws + NotProvisionedException, ResourceBusyException { + byte[] sessionId = openSessionNative(level); + mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponentImpl(sessionId)); + return sessionId; + } + + @NonNull + private native byte[] openSessionNative(int level) throws NotProvisionedException, ResourceBusyException; /** * Close a session on the MediaDrm object that was previously opened * with {@link #openSession}. */ - public native void closeSession(@NonNull byte[] sessionId); + public void closeSession(@NonNull byte[] sessionId) { + closeSessionNative(sessionId); + mPlaybackComponentMap.remove(ByteBuffer.wrap(sessionId)); + } + + private native void closeSessionNative(@NonNull byte[] sessionId); + + private final Map<ByteBuffer, PlaybackComponent> mPlaybackComponentMap + = new ConcurrentHashMap<>(); /** * This key request type species that the keys will be for online use, they will @@ -2056,6 +2073,7 @@ public final class MediaDrm implements AutoCloseable { mCloseGuard.close(); if (mClosed.compareAndSet(false, true)) { native_release(); + mPlaybackComponentMap.clear(); } } @@ -2430,4 +2448,49 @@ public final class MediaDrm implements AutoCloseable { public static final String EVENT_SESSION_RECLAIMED_COUNT = "drm.mediadrm.event.SESSION_RECLAIMED.count"; } + + /** + * Obtain a {@link PlaybackComponent} associated with a DRM session. + * Call {@link PlaybackComponent#setPlaybackId(String)} on the returned object + * to associate a playback session with the DRM session. + * + * @param sessionId a DRM session ID obtained from {@link #openSession()} + * @return a {@link PlaybackComponent} associated with the session, + * or {@code null} if the session is closed or does not exist. + * @see PlaybackComponent + * @hide + */ + @TestApi + @Nullable + public PlaybackComponent getPlaybackComponent(@NonNull byte[] sessionId) { + if (sessionId == null) { + throw new IllegalArgumentException("sessionId is null"); + } + return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId)); + } + + private native void setPlaybackId(byte[] sessionId, String playbackId); + + private final class PlaybackComponentImpl implements PlaybackComponent { + private final byte[] mSessionId; + private String mPlaybackId = ""; + + public PlaybackComponentImpl(byte[] sessionId) { + mSessionId = sessionId; + } + + @Override + public void setPlaybackId(@NonNull String playbackId) { + if (playbackId == null) { + throw new IllegalArgumentException("playbackId is null"); + } + MediaDrm.this.setPlaybackId(mSessionId, playbackId); + mPlaybackId = playbackId; + } + + @Override + @NonNull public String getPlaybackId() { + return mPlaybackId; + } + } } diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java index 625dd0a1870d..94e55b4843c1 100644 --- a/media/java/android/media/metrics/PlaybackComponent.java +++ b/media/java/android/media/metrics/PlaybackComponent.java @@ -17,11 +17,13 @@ package android.media.metrics; import android.annotation.NonNull; +import android.annotation.TestApi; /** * Interface for playback related components used by playback metrics. * @hide */ +@TestApi public interface PlaybackComponent { /** diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index 4f27b197273c..6141b7fc7463 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -769,10 +769,12 @@ public class MtpDatabase implements AutoCloseable { try { Log.i(TAG, "openFile with transcode support: " + path); - // TODO(b/158466651): Pass the |transcode| variable as flag to openFile - Bundle bundle = null; - if (!transcode) { - bundle = new Bundle(); + Bundle bundle = new Bundle(); + if (transcode) { + bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, + new ApplicationMediaCapabilities.Builder().addUnsupportedVideoMimeType( + MediaFormat.MIMETYPE_VIDEO_HEVC).build()); + } else { bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType( MediaFormat.MIMETYPE_VIDEO_HEVC).build()); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index babb16b1c880..0e8719eeb79c 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -1978,6 +1978,24 @@ static jboolean android_media_MediaDrm_requiresSecureDecoder( return drm->requiresSecureDecoder(mimeType.c_str(), securityLevel); } +static void android_media_MediaDrm_setPlaybackId( + JNIEnv *env, jobject thiz, jbyteArray jsessionId, + jstring jplaybackId) { + sp<IDrm> drm = GetDrm(env, thiz); + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + String8 playbackId; + if (jplaybackId != NULL) { + playbackId = JStringToString8(env, jplaybackId); + } + status_t err = drm->setPlaybackId(sessionId, playbackId.c_str()); + throwExceptionAsNecessary(env, err, "Failed to set playbackId"); +} + static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaDrm_native_release }, @@ -1992,10 +2010,10 @@ static const JNINativeMethod gMethods[] = { { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, - { "openSession", "(I)[B", + { "openSessionNative", "(I)[B", (void *)android_media_MediaDrm_openSession }, - { "closeSession", "([B)V", + { "closeSessionNative", "([B)V", (void *)android_media_MediaDrm_closeSession }, { "getKeyRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)" @@ -2102,6 +2120,9 @@ static const JNINativeMethod gMethods[] = { { "requiresSecureDecoder", "(Ljava/lang/String;I)Z", (void *)android_media_MediaDrm_requiresSecureDecoder }, + + { "setPlaybackId", "([BLjava/lang/String;)V", + (void *)android_media_MediaDrm_setPlaybackId }, }; int register_android_media_Drm(JNIEnv *env) { diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp index 7f3916fe634d..77583b8032c8 100644 --- a/media/jni/tuner/LnbClient.cpp +++ b/media/jni/tuner/LnbClient.cpp @@ -19,6 +19,7 @@ #include <android-base/logging.h> #include <utils/Log.h> +#include "TunerClient.h" #include "LnbClient.h" using ::android::hardware::tv::tuner::V1_0::Result; @@ -27,14 +28,13 @@ namespace android { /////////////// LnbClient /////////////////////// -// TODO: pending aidl interface -LnbClient::LnbClient() { - //mTunerLnb = tunerLnb; +LnbClient::LnbClient(shared_ptr<ITunerLnb> tunerLnb) { + mTunerLnb = tunerLnb; mId = -1; } LnbClient::~LnbClient() { - //mTunerLnb = NULL; + mTunerLnb = NULL; mLnb = NULL; mId = -1; } @@ -45,19 +45,21 @@ void LnbClient::setHidlLnb(sp<ILnb> lnb) { } Result LnbClient::setCallback(sp<LnbClientCallback> cb) { - // TODO: pending aidl interface - /*if (mTunerFrontend != NULL) { + if (mTunerLnb != NULL) { mAidlCallback = ::ndk::SharedRefBase::make<TunerLnbCallback>(cb); - mTunerLnb->setCallback(mAidlCallback); - return Result::SUCCESS; - }*/ + Status s = mTunerLnb->setCallback(mAidlCallback); + return TunerClient::getServiceSpecificErrorCode(s); + } mHidlCallback = new HidlLnbCallback(cb); return mLnb->setCallback(mHidlCallback); } Result LnbClient::setVoltage(LnbVoltage voltage) { - // TODO: pending aidl interface + if (mTunerLnb != NULL) { + Status s = mTunerLnb->setVoltage((int)voltage); + return TunerClient::getServiceSpecificErrorCode(s); + } if (mLnb != NULL) { return mLnb->setVoltage(voltage); @@ -67,7 +69,10 @@ Result LnbClient::setVoltage(LnbVoltage voltage) { } Result LnbClient::setTone(LnbTone tone) { - // TODO: pending aidl interface + if (mTunerLnb != NULL) { + Status s = mTunerLnb->setTone((int)tone); + return TunerClient::getServiceSpecificErrorCode(s); + } if (mLnb != NULL) { return mLnb->setTone(tone); @@ -77,7 +82,10 @@ Result LnbClient::setTone(LnbTone tone) { } Result LnbClient::setSatellitePosition(LnbPosition position) { - // TODO: pending aidl interface + if (mTunerLnb != NULL) { + Status s = mTunerLnb->setSatellitePosition((int)position); + return TunerClient::getServiceSpecificErrorCode(s); + } if (mLnb != NULL) { return mLnb->setSatellitePosition(position); @@ -87,7 +95,10 @@ Result LnbClient::setSatellitePosition(LnbPosition position) { } Result LnbClient::sendDiseqcMessage(vector<uint8_t> diseqcMessage) { - // TODO: pending aidl interface + if (mTunerLnb != NULL) { + Status s = mTunerLnb->sendDiseqcMessage(diseqcMessage); + return TunerClient::getServiceSpecificErrorCode(s); + } if (mLnb != NULL) { return mLnb->sendDiseqcMessage(diseqcMessage); @@ -97,7 +108,10 @@ Result LnbClient::sendDiseqcMessage(vector<uint8_t> diseqcMessage) { } Result LnbClient::close() { - // TODO: pending aidl interface + if (mTunerLnb != NULL) { + Status s = mTunerLnb->close(); + return TunerClient::getServiceSpecificErrorCode(s); + } if (mLnb != NULL) { return mLnb->close(); @@ -125,6 +139,25 @@ Return<void> HidlLnbCallback::onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMes return Void(); } -/////////////// LnbClient Helper Methods /////////////////////// +/////////////// TunerLnbCallback /////////////////////// + +TunerLnbCallback::TunerLnbCallback(sp<LnbClientCallback> lnbClientCallback) + : mLnbClientCallback(lnbClientCallback) {} +Status TunerLnbCallback::onEvent(int lnbEventType) { + if (mLnbClientCallback != NULL) { + mLnbClientCallback->onEvent(static_cast<LnbEventType>(lnbEventType)); + return Status::ok(); + } + return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_STATE)); +} + +Status TunerLnbCallback::onDiseqcMessage(const vector<uint8_t>& diseqcMessage) { + if (mLnbClientCallback != NULL) { + hidl_vec<uint8_t> msg(begin(diseqcMessage), end(diseqcMessage)); + mLnbClientCallback->onDiseqcMessage(msg); + return Status::ok(); + } + return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_STATE)); +} } // namespace android diff --git a/media/jni/tuner/LnbClient.h b/media/jni/tuner/LnbClient.h index 533a99678efe..24651202d73e 100644 --- a/media/jni/tuner/LnbClient.h +++ b/media/jni/tuner/LnbClient.h @@ -17,14 +17,18 @@ #ifndef _ANDROID_MEDIA_TV_LNB_CLIENT_H_ #define _ANDROID_MEDIA_TV_LNB_CLIENT_H_ -//#include <aidl/android/media/tv/tuner/ITunerLnb.h> +#include <aidl/android/media/tv/tuner/BnTunerLnbCallback.h> +#include <aidl/android/media/tv/tuner/ITunerLnb.h> #include <android/hardware/tv/tuner/1.0/ILnb.h> #include <android/hardware/tv/tuner/1.0/ILnbCallback.h> #include <android/hardware/tv/tuner/1.1/types.h> #include "LnbClientCallback.h" -//using ::aidl::android::media::tv::tuner::ITunerLnb; +using Status = ::ndk::ScopedAStatus; + +using ::aidl::android::media::tv::tuner::BnTunerLnbCallback; +using ::aidl::android::media::tv::tuner::ITunerLnb; using ::android::hardware::Return; using ::android::hardware::Void; @@ -42,17 +46,17 @@ using namespace std; namespace android { // TODO: pending aidl interface -/*class TunerLnbCallback : public BnTunerLnbCallback { +class TunerLnbCallback : public BnTunerLnbCallback { public: TunerLnbCallback(sp<LnbClientCallback> lnbClientCallback); Status onEvent(int lnbEventType); - Status onDiseqcMessage(vector<uint8_t> diseqcMessage); + Status onDiseqcMessage(const vector<uint8_t>& diseqcMessage); private: sp<LnbClientCallback> mLnbClientCallback; -};*/ +}; struct HidlLnbCallback : public ILnbCallback { @@ -68,8 +72,7 @@ private: struct LnbClient : public RefBase { public: - // TODO: add TunerLnb as parameter. - LnbClient(); + LnbClient(shared_ptr<ITunerLnb> tunerLnb); ~LnbClient(); // TODO: remove after migration to Tuner Service is done. @@ -114,8 +117,7 @@ private: * An AIDL Tuner Lnb Singleton assigned at the first time the Tuner Client * opens an Lnb. Default null when lnb is not opened. */ - // TODO: pending on aidl interface - //shared_ptr<ITunerLnb> mTunerLnb; + shared_ptr<ITunerLnb> mTunerLnb; /** * A Lnb HAL interface that is ready before migrating to the TunerLnb. @@ -124,7 +126,7 @@ private: */ sp<ILnb> mLnb; - //shared_ptr<TunerLnbCallback> mAidlCallback; + shared_ptr<TunerLnbCallback> mAidlCallback; sp<HidlLnbCallback> mHidlCallback; LnbId mId; diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp index f5e35248fcfd..498ba0eabb70 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -229,15 +229,15 @@ sp<DescramblerClient> TunerClient::openDescrambler(int /*descramblerHandle*/) { sp<LnbClient> TunerClient::openLnb(int lnbHandle) { if (mTunerService != NULL) { // TODO: handle error code - /*shared_ptr<ITunerLnb> tunerLnb; - mTunerService->openLnb(demuxHandle, &tunerLnb); - return new LnbClient(tunerLnb);*/ + shared_ptr<ITunerLnb> tunerLnb; + mTunerService->openLnb(lnbHandle, &tunerLnb); + return new LnbClient(tunerLnb); } if (mTuner != NULL) { int id = getResourceIdFromHandle(lnbHandle, LNB); // TODO: pending aidl interface - sp<LnbClient> lnbClient = new LnbClient(); + sp<LnbClient> lnbClient = new LnbClient(NULL); sp<ILnb> hidlLnb = openHidlLnbById(id); if (hidlLnb != NULL) { lnbClient->setHidlLnb(hidlLnb); @@ -252,14 +252,14 @@ sp<LnbClient> TunerClient::openLnb(int lnbHandle) { sp<LnbClient> TunerClient::openLnbByName(string lnbName) { if (mTunerService != NULL) { // TODO: handle error code - /*shared_ptr<ITunerLnb> tunerLnb; + shared_ptr<ITunerLnb> tunerLnb; mTunerService->openLnbByName(lnbName, &tunerLnb); - return new LnbClient(tunerLnb);*/ + return new LnbClient(tunerLnb); } if (mTuner != NULL) { // TODO: pending aidl interface - sp<LnbClient> lnbClient = new LnbClient(); + sp<LnbClient> lnbClient = new LnbClient(NULL); LnbId id; sp<ILnb> hidlLnb = openHidlLnbByName(lnbName, id); if (hidlLnb != NULL) { diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h index 8a1181a38fe2..733ee6bff9b7 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -20,6 +20,7 @@ #include <aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.h> #include <aidl/android/media/tv/tuner/ITunerService.h> #include <aidl/android/media/tv/tuner/TunerFrontendInfo.h> +#include <android/binder_parcel_utils.h> #include <android/hardware/tv/tuner/1.1/ITuner.h> #include <android/hardware/tv/tuner/1.1/types.h> @@ -28,6 +29,8 @@ #include "DescramblerClient.h" #include "LnbClient.h" +using Status = ::ndk::ScopedAStatus; + using ::aidl::android::media::tv::tuner::ITunerService; using ::aidl::android::media::tv::tuner::TunerFrontendInfo; using ::aidl::android::media::tv::tunerresourcemanager::ITunerResourceManager; @@ -132,6 +135,15 @@ public: */ int getHalTunerVersion() { return mTunerVersion; } + static Result getServiceSpecificErrorCode(Status& s) { + if (s.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast<Result>(s.getServiceSpecificError()); + } else if (s.isOk()) { + return Result::SUCCESS; + } + return Result::UNKNOWN_ERROR; + } + private: sp<ITuner> getHidlTuner(); sp<IFrontend> openHidlFrontendById(int id); diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml index ead68e3ba18e..b643eb2936ce 100644 --- a/packages/CompanionDeviceManager/res/values-de/strings.xml +++ b/packages/CompanionDeviceManager/res/values-de/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"Begleitgerät-Manager"</string> <string name="chooser_title" msgid="2262294130493605839">"Gerät (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) auswählen, das von <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> verwaltet werden soll"</string> <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string> <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> zum Verwalten deines Geräts (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) festlegen – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist erforderlich, um dein Gerät (<xliff:g id="PROFILE_NAME">%2$s</xliff:g>) zu verwalten. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"Ja"</string> <string name="consent_no" msgid="1335543792857823917">"Nein danke"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml index e99a3cdef2e3..7e4104208446 100644 --- a/packages/CompanionDeviceManager/res/values-gu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"કમ્પેનિયન ડિવાઇસ મેનેજર"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string> <string name="confirmation_title" msgid="4751119145078041732">"તમારા <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>ને મેનેજ કરવા માટે <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> સેટ કરો"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"તમારા <xliff:g id="PROFILE_NAME">%2$s</xliff:g>ને મેનેજ કરવા માટે <xliff:g id="APP_NAME">%1$s</xliff:g> જરૂરી છે. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"હા"</string> <string name="consent_no" msgid="1335543792857823917">"ના, આભાર"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml index 33950eb373c7..8663e56ecf14 100644 --- a/packages/CompanionDeviceManager/res/values-iw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"ניהול מכשיר מותאם"</string> <string name="chooser_title" msgid="2262294130493605839">"בחירה של <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string> <string name="confirmation_title" msgid="4751119145078041732">"הגדרה של <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> לניהול <xliff:g id="PROFILE_NAME">%2$s</xliff:g> – <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> נדרשת לניהול של <xliff:g id="PROFILE_NAME">%2$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"כן"</string> <string name="consent_no" msgid="1335543792857823917">"לא תודה"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml index b695d9ded477..ca17336bfb23 100644 --- a/packages/CompanionDeviceManager/res/values-ja/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4470785958457506021">"コンパニオン デバイス マネージャ"</string> - <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> の管理対象となる <xliff:g id="PROFILE_NAME">%1$s</xliff:g> の選択"</string> + <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string> <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string> <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string> <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> で <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> を管理するよう設定する"</string> diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml index f4ae18f77847..0225166849fd 100644 --- a/packages/CompanionDeviceManager/res/values-kn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"ಕಂಪ್ಯಾನಿಯನ್ ಸಾಧನ ನಿರ್ವಾಹಕರು"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string> <string name="confirmation_title" msgid="4751119145078041732">"ನಿಮ್ಮ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಲು, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಿ"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು, <xliff:g id="APP_NAME">%1$s</xliff:g> ಅಗತ್ಯವಿದೆ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"ಹೌದು"</string> <string name="consent_no" msgid="1335543792857823917">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml index 68f9109762fe..144698b9bc4e 100644 --- a/packages/CompanionDeviceManager/res/values-mr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"सहयोगी डिव्हाइस व्यवस्थापक"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string> <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"पाहा"</string> <string name="confirmation_title" msgid="4751119145078041732">"तुमची <xliff:g id="PROFILE_NAME">%2$s</xliff:g> व्यवस्थापित करण्यासाठी <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट करा - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"तुमची <xliff:g id="PROFILE_NAME">%2$s</xliff:g> व्यवस्थापित करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> आवश्यक आहे. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"होय"</string> <string name="consent_no" msgid="1335543792857823917">"नाही, नको"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml index 1188922607b9..7bea2c91fd2d 100644 --- a/packages/CompanionDeviceManager/res/values-ms/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"Pengurus Peranti Rakan"</string> <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string> <string name="confirmation_title" msgid="4751119145078041732">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengurus <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> anda"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"<xliff:g id="APP_NAME">%1$s</xliff:g> diperlukan untuk mengurus <xliff:g id="PROFILE_NAME">%2$s</xliff:g> anda. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"Ya"</string> <string name="consent_no" msgid="1335543792857823917">"Tidak perlu"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml index d1aa50b6c30f..c8c680f8f3a9 100644 --- a/packages/CompanionDeviceManager/res/values-or/strings.xml +++ b/packages/CompanionDeviceManager/res/values-or/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"ସହଯୋଗୀ ଡିଭାଇସ୍ ପରିଚାଳକ"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string> <string name="confirmation_title" msgid="4751119145078041732">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%2$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>କୁ ସେଟ୍ କରନ୍ତୁ - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%2$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆବଶ୍ୟକ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"ହଁ"</string> <string name="consent_no" msgid="1335543792857823917">"ନା, ଧନ୍ୟବାଦ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml index ff211f2550d5..0da94105a576 100644 --- a/packages/CompanionDeviceManager/res/values-pa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"ਸੰਬੰਧੀ ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਕ"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string> <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string> <string name="confirmation_title" msgid="4751119145078041732">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ਨੂੰ ਤੁਹਾਡਾ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਸੈੱਟ ਕਰੋ"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"ਤੁਹਾਡੇ <xliff:g id="PROFILE_NAME">%2$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ <xliff:g id="APP_NAME">%1$s</xliff:g> ਦੀ ਲੋੜ ਹੈ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"ਹਾਂ"</string> <string name="consent_no" msgid="1335543792857823917">"ਨਹੀਂ ਧੰਨਵਾਦ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml index 4c308e8c5b67..6fa759c15905 100644 --- a/packages/CompanionDeviceManager/res/values-sq/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"Menaxheri i pajisjes shoqëruese"</string> <string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string> <string name="confirmation_title" msgid="4751119145078041732">"Cakto <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> që të menaxhojë profilin tënd <xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"Nevojitet <xliff:g id="APP_NAME">%1$s</xliff:g> për të menaxhuar profilin tënd të <xliff:g id="PROFILE_NAME">%2$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"Po"</string> <string name="consent_no" msgid="1335543792857823917">"Jo, faleminderit"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml index 967b7f9b5a25..dce18152dff4 100644 --- a/packages/CompanionDeviceManager/res/values-ur/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml @@ -19,11 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"ساتھی آلہ مینیجر"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string> <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string> - <!-- no translation found for profile_name_watch (576290739483672360) --> - <skip /> + <string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string> <string name="confirmation_title" msgid="4751119145078041732">"اپنے <xliff:g id="PROFILE_NAME">%2$s</xliff:g> کا نظم کرنے کے لیے <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کو سیٹ کریں - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong>"</string> - <!-- no translation found for profile_summary (2009764182871566255) --> - <skip /> + <string name="profile_summary" msgid="2009764182871566255">"آپ کے <xliff:g id="PROFILE_NAME">%2$s</xliff:g> کا نظم کرنے کے لیے <xliff:g id="APP_NAME">%1$s</xliff:g> کی ضرورت ہے۔ <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"ہاں"</string> <string name="consent_no" msgid="1335543792857823917">"نہیں شکریہ"</string> </resources> diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml index 4cce2e8b7eb1..2ca27b530651 100644 --- a/packages/CompanionDeviceManager/res/values-uz/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml @@ -19,9 +19,9 @@ <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string> <string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string> <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string> - <string name="profile_name_watch" msgid="576290739483672360">"tomosha qilish"</string> + <string name="profile_name_watch" msgid="576290739483672360">"soat"</string> <string name="confirmation_title" msgid="4751119145078041732">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> - <strong><xliff:g id="DEVICE_NAME">%3$s</xliff:g></strong> qurilmalarini boshqarish uchun <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ilovasini sozlang"</string> - <string name="profile_summary" msgid="2009764182871566255">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="PROFILE_NAME">%2$s</xliff:g> qurilmasini boshqarish uchun kerak. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> + <string name="profile_summary" msgid="2009764182871566255">"<xliff:g id="PROFILE_NAME">%2$s</xliff:g> qurilmasini boshqarish uchun <xliff:g id="APP_NAME">%1$s</xliff:g> zarur. <xliff:g id="PRIVILEGES_DISCPLAIMER">%3$s</xliff:g>"</string> <string name="consent_yes" msgid="4055438216605487056">"Ha"</string> <string name="consent_no" msgid="1335543792857823917">"Kerak emas"</string> </resources> diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt b/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt index 56f599a3a219..7efaf0b37de1 100644 --- a/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt +++ b/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt @@ -74,6 +74,7 @@ public class NekoControlsService : ControlsProviderService(), PrefState.PrefsLis private val controls = HashMap<String, Control>() private val publishers = ArrayList<UglyPublisher>() private val rng = Random() + private val metricsLogger = MetricsLogger() private var lastToyIcon: Icon? = null @@ -184,7 +185,6 @@ public class NekoControlsService : ControlsProviderService(), PrefState.PrefsLis return getPendingIntent() } - override fun performControlAction( controlId: String, action: ControlAction, @@ -196,7 +196,7 @@ public class NekoControlsService : ControlsProviderService(), PrefState.PrefsLis controls[CONTROL_ID_FOOD] = makeFoodBowlControl(true) Log.v(TAG, "Bowl refilled. (Registering job.)") NekoService.registerJob(this, FOOD_SPAWN_CAT_DELAY_MINS) - MetricsLogger.histogram(this, "egg_neko_offered_food", 11) + metricsLogger.histogram("egg_neko_offered_food", 11) prefs.foodState = 11 } CONTROL_ID_TOY -> { diff --git a/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt b/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt index 460fa3a7241f..20527afd9652 100644 --- a/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt +++ b/packages/EasterEgg/src/com/android/egg/paint/ToolbarView.kt @@ -47,6 +47,7 @@ class ToolbarView : FrameLayout { ) : super(context, attrs, defStyle) { } + @Suppress("DEPRECATION") override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets { var lp = layoutParams as FrameLayout.LayoutParams? if (lp != null && insets != null) { diff --git a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt index ce439a9a663c..578de01a0c30 100644 --- a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt +++ b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt @@ -49,6 +49,7 @@ class QuaresActivity : Activity() { private lateinit var label: Button private lateinit var grid: GridLayout + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/packages/InputDevices/res/values-eu/strings.xml b/packages/InputDevices/res/values-eu/strings.xml index eb95466580fc..0346d74ca7a7 100644 --- a/packages/InputDevices/res/values-eu/strings.xml +++ b/packages/InputDevices/res/values-eu/strings.xml @@ -44,7 +44,7 @@ <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituaniera"</string> <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espainiera (Latinoamerika)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letoniera"</string> - <string name="keyboard_layout_persian" msgid="3920643161015888527">"Pertsiera"</string> + <string name="keyboard_layout_persian" msgid="3920643161015888527">"Persiarra"</string> <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Azerbaijandarra"</string> <string name="keyboard_layout_polish" msgid="1121588624094925325">"Poloniarra"</string> <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Bielorrusiera"</string> diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml index 1833329a9e34..b25b37bec795 100644 --- a/packages/PackageInstaller/res/values-ca/strings.xml +++ b/packages/PackageInstaller/res/values-ca/strings.xml @@ -80,9 +80,9 @@ <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Les accions d\'instal·lar o de desinstal·lar no s\'admeten a Wear."</string> <string name="message_staging" msgid="8032722385658438567">"S\'està preparant la instal·lació de l\'aplicació…"</string> <string name="app_name_unknown" msgid="6881210203354323926">"Desconeguda"</string> - <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Per seguretat, la tauleta no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> - <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Per seguretat, el televisor no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> - <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Per seguretat, el telèfon no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> + <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Per la teva seguretat, la tauleta no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> + <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Per la teva seguretat, el televisor no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> + <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Per la teva seguretat, el telèfon no pot instal·lar aplicacions desconegudes d\'aquesta font."</string> <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"El telèfon i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi al telèfon o de la pèrdua de dades que pugui resultar del seu ús."</string> <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"La tauleta i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi a la tauleta o de la pèrdua de dades que pugui resultar del seu ús."</string> <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"El televisor i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi al televisor o de la pèrdua de dades que pugui resultar del seu ús."</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java index 02d1c2e65c8d..64cb0f1b7885 100644 --- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java @@ -210,7 +210,8 @@ public class ConnectivitySubsystemsRecoveryManager { } private void checkIfAllSubsystemsRestartsAreDone() { - if (!mWifiRestartInProgress && !mTelephonyRestartInProgress) { + if (!mWifiRestartInProgress && !mTelephonyRestartInProgress + && mCurrentRecoveryCallback != null) { mCurrentRecoveryCallback.onSubsystemRestartOperationEnd(); mCurrentRecoveryCallback = null; } @@ -283,8 +284,10 @@ public class ConnectivitySubsystemsRecoveryManager { stopTrackingTelephonyRestart(); mWifiRestartInProgress = false; mTelephonyRestartInProgress = false; - mCurrentRecoveryCallback.onSubsystemRestartOperationEnd(); - mCurrentRecoveryCallback = null; + if (mCurrentRecoveryCallback != null) { + mCurrentRecoveryCallback.onSubsystemRestartOperationEnd(); + mCurrentRecoveryCallback = null; + } }, RESTART_TIMEOUT_MS); } }); diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt index b2e75ea6867f..cd22247d6142 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt @@ -38,14 +38,13 @@ class Estimate( * @return An [Estimate] object with the latest battery estimates. */ @JvmStatic + @Suppress("DEPRECATION") fun getCachedEstimateIfAvailable(context: Context): Estimate? { // if time > 2 min return null or the estimate otherwise val resolver = context.contentResolver - val lastUpdateTime = Instant.ofEpochMilli( - Settings.Global.getLong( - resolver, Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME, -1)) + val lastUpdateTime = getLastCacheUpdateTime(context) return if (Duration.between(lastUpdateTime, - Instant.now()).compareTo(Duration.ofMinutes(1)) > 0) { + Instant.now()) > Duration.ofMinutes(1)) { null } else Estimate( Settings.Global.getLong(resolver, @@ -65,6 +64,7 @@ class Estimate( * @param estimate the [Estimate] object to store */ @JvmStatic + @Suppress("DEPRECATION") fun storeCachedEstimate(context: Context, estimate: Estimate) { // store the estimate and update the timestamp val resolver = context.contentResolver @@ -82,6 +82,7 @@ class Estimate( * Returns when the estimate was last updated as an Instant */ @JvmStatic + @Suppress("DEPRECATION") fun getLastCacheUpdateTime(context: Context): Instant { return Instant.ofEpochMilli( Settings.Global.getLong( diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 86b5d0d7d42f..ca9dcd62b3ca 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -433,6 +433,15 @@ android:excludeFromRecents="true"> </activity> + <!-- started from SensoryPrivacyService --> + <activity android:name=".sensorprivacy.SensorUseStartedActivity" + android:exported="true" + android:permission="android.permission.MANAGE_SENSOR_PRIVACY" + android:theme="@style/Theme.SystemUI.Dialog.Alert" + android:finishOnCloseSystemDialogs="true"> + </activity> + + <!-- started from UsbDeviceSettingsManager --> <activity android:name=".usb.UsbAccessoryUriActivity" android:exported="true" diff --git a/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml index 21c9051157d1..b844515f1088 100644 --- a/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml +++ b/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml @@ -13,8 +13,13 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<vector android:height="36sp" android:viewportHeight="36" - android:viewportWidth="36" android:width="36sp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="?android:attr/colorAccent" android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"/> - <path android:fillColor="?android:attr/textColorPrimaryInverse" android:pathData="M17.59,13.41L21.17,17H7v2h14.17l-3.59,3.59L19,24l6,-6l-6,-6L17.59,13.41zM26,12v12h2V12H26z"/> -</vector>
\ No newline at end of file +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:height="36sp" + android:viewportHeight="36" + android:viewportWidth="36" + android:width="36sp"> + <path android:fillColor="?android:attr/colorBackground" + android:pathData="M17.59,13.41L21.17,17H7v2h14.17l-3.59,3.59L19,24l6,-6l-6,-6L17.59, + 13.41zM26,12v12h2V12H26z"/> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml new file mode 100644 index 000000000000..b7a9fafd0c44 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* 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. +*/ +--> +<shape + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="?android:attr/colorBackground" /> + <corners android:radius="10dp" /> +</shape> diff --git a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml deleted file mode 100644 index 51c442abf2fd..000000000000 --- a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight" - android:radius="40dp"/> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml index 72591d4665c9..411fea5dd22d 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml @@ -17,15 +17,18 @@ <merge xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/digit_text" - style="@style/Widget.TextView.NumPadKey" + style="@style/Widget.TextView.NumPadKey.Digit" android:layout_width="wrap_content" android:layout_height="wrap_content" /> + <!-- b/172360102: Setting visibility to gone for now, in order to see if there are many + issues with removing the alpha characters. --> <TextView android:id="@+id/klondike_text" style="@style/Widget.TextView.NumPadKey.Klondike" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:visibility="gone" /> </merge> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index 87c98d2e9597..aa14645a6093 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -24,7 +24,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_max_height" android:orientation="vertical" > <LinearLayout @@ -34,37 +33,34 @@ android:orientation="vertical" android:layout_weight="1" android:layoutDirection="ltr" + android:layout_marginBottom="8dp" > - <com.android.keyguard.AlphaOptimizedRelativeLayout - android:id="@+id/row0" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:paddingBottom="16dp" - > + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + /> + <com.android.keyguard.AlphaOptimizedRelativeLayout + android:id="@+id/row0" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="16dp" + > <com.android.keyguard.PasswordTextView android:id="@+id/pinEntry" android:layout_width="@dimen/keyguard_security_width" - android:layout_height="match_parent" + android:layout_height="@dimen/keyguard_password_height" style="@style/Widget.TextView.Password" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" androidprv:scaledTextSize="@integer/scaled_password_text_size" android:contentDescription="@string/keyguard_accessibility_pin_area" /> - <View - android:id="@+id/divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_alignParentBottom="true" - android:background="@drawable/pin_divider" - /> </com.android.keyguard.AlphaOptimizedRelativeLayout> <LinearLayout android:id="@+id/row1" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -95,8 +91,7 @@ <LinearLayout android:id="@+id/row2" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -127,9 +122,8 @@ <LinearLayout android:id="@+id/row3" android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="wrap_content" android:orientation="horizontal" - android:layout_weight="1" > <com.android.keyguard.NumPadKey android:id="@+id/key7" @@ -159,18 +153,16 @@ <LinearLayout android:id="@+id/row4" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/delete_button" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - android:background="@drawable/ripple_drawable_pin" android:contentDescription="@string/keyboardview_keycode_delete" - style="@style/Keyguard.ImageButton.NumPadDelete" + style="@style/NumPadKey.Delete" /> <com.android.keyguard.NumPadKey android:id="@+id/key0" @@ -180,13 +172,12 @@ androidprv:textView="@+id/pinEntry" androidprv:digit="0" /> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/key_enter" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - style="@style/Keyguard.ImageButton.NumPadEnter" - android:background="@drawable/ripple_drawable_pin" + style="@style/NumPadKey.Enter" android:contentDescription="@string/keyboardview_keycode_enter" /> </LinearLayout> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index 912d7bbf7ef5..64ccefd2e4ee 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -25,9 +25,14 @@ android:layout_width="match_parent" android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_max_height" android:gravity="center_horizontal"> + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + /> + <ImageView android:id="@+id/keyguard_sim" android:layout_width="match_parent" @@ -37,10 +42,9 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="wrap_content" android:orientation="vertical" android:gravity="center" - android:layout_weight="1" android:layoutDirection="ltr" > <include layout="@layout/keyguard_esim_area" @@ -52,32 +56,23 @@ <RelativeLayout android:id="@+id/row0" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:paddingBottom="16dp" > <com.android.keyguard.PasswordTextView android:id="@+id/simPinEntry" android:layout_width="@dimen/keyguard_security_width" - android:layout_height="match_parent" + android:layout_height="@dimen/keyguard_password_height" android:gravity="center" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" androidprv:scaledTextSize="@integer/scaled_password_text_size" android:contentDescription="@string/keyguard_accessibility_sim_pin_area" /> - <View - android:id="@+id/divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_alignParentBottom="true" - android:background="@drawable/pin_divider" - /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -107,8 +102,7 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -138,9 +132,8 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="wrap_content" android:orientation="horizontal" - android:layout_weight="1" > <com.android.keyguard.NumPadKey android:id="@+id/key7" @@ -169,18 +162,16 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/delete_button" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - android:background="@drawable/ripple_drawable_pin" android:contentDescription="@string/keyboardview_keycode_delete" - style="@style/Keyguard.ImageButton.NumPadDelete" + style="@style/NumPadKey.Delete" /> <com.android.keyguard.NumPadKey android:id="@+id/key0" @@ -190,13 +181,12 @@ androidprv:textView="@+id/simPinEntry" androidprv:digit="0" /> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/key_enter" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - style="@style/Keyguard.ImageButton.NumPadEnter" - android:background="@drawable/ripple_drawable_pin" + style="@style/NumPadKey.Enter" android:contentDescription="@string/keyboardview_keycode_enter" /> </LinearLayout> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index 81b49648ab62..dc77bd356e55 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -26,9 +26,14 @@ android:layout_width="match_parent" android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_max_height" android:gravity="center_horizontal"> + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + /> + <ImageView android:id="@+id/keyguard_sim" android:layout_width="match_parent" @@ -38,7 +43,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="wrap_content" android:orientation="vertical" android:gravity="center" android:layout_weight="1" @@ -53,32 +58,23 @@ <RelativeLayout android:id="@+id/row0" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:paddingBottom="16dp" > <com.android.keyguard.PasswordTextView android:id="@+id/pukEntry" android:layout_width="@dimen/keyguard_security_width" - android:layout_height="match_parent" + android:layout_height="@dimen/keyguard_password_height" android:gravity="center" android:layout_centerHorizontal="true" android:layout_marginRight="72dp" androidprv:scaledTextSize="@integer/scaled_password_text_size" android:contentDescription="@string/keyguard_accessibility_sim_puk_area" /> - <View - android:id="@+id/divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_alignParentBottom="true" - android:background="@drawable/pin_divider" - /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -108,8 +104,7 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > <com.android.keyguard.NumPadKey @@ -139,9 +134,8 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="wrap_content" android:orientation="horizontal" - android:layout_weight="1" > <com.android.keyguard.NumPadKey android:id="@+id/key7" @@ -170,18 +164,16 @@ </LinearLayout> <LinearLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" + android:layout_height="wrap_content" android:orientation="horizontal" > - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/delete_button" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - android:background="@drawable/ripple_drawable_pin" android:contentDescription="@string/keyboardview_keycode_delete" - style="@style/Keyguard.ImageButton.NumPadDelete" + style="@style/NumPadKey.Delete" /> <com.android.keyguard.NumPadKey android:id="@+id/key0" @@ -191,13 +183,12 @@ androidprv:textView="@+id/pukEntry" androidprv:digit="0" /> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.keyguard.NumPadButton android:id="@+id/key_enter" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - style="@style/Keyguard.ImageButton.NumPadEnter" - android:background="@drawable/ripple_drawable_pin" + style="@style/NumPadKey.Enter" android:contentDescription="@string/keyboardview_keycode_enter" /> </LinearLayout> diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml index bfcc56cdc660..eb7a1f73fbc9 100644 --- a/packages/SystemUI/res-keyguard/values/attrs.xml +++ b/packages/SystemUI/res-keyguard/values/attrs.xml @@ -40,6 +40,8 @@ <attr name="passwordStyle" format="reference" /> + <attr name="numPadKeyStyle" format="reference" /> + <declare-styleable name="AnimatableClockView"> <attr name="dozeWeight" format="integer" /> <attr name="lockScreenWeight" format="integer" /> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index f9389ce24d96..aa87107f954f 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -33,6 +33,9 @@ (includes 2x keyguard_security_view_top_margin) --> <dimen name="keyguard_security_max_height">450dp</dimen> + <!-- pin/password field max height --> + <dimen name="keyguard_password_height">80dp</dimen> + <!-- Margin around the various security views --> <dimen name="keyguard_security_view_top_margin">8dp</dimen> <dimen name="keyguard_security_view_lateral_margin">36dp</dimen> @@ -81,4 +84,7 @@ <!-- The translation for disappearing security views after having solved them. --> <dimen name="disappear_y_translation">-32dp</dimen> + + <!-- Spacing around each button used for PIN view --> + <dimen name="num_pad_key_margin">2dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 71a1cc292ec9..2e99dea6e18b 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -30,7 +30,13 @@ <item name="android:paddingLeft">12dp</item> <item name="android:paddingRight">12dp</item> </style> - <style name="Widget.TextView.NumPadKey" parent="@android:style/Widget.DeviceDefault.TextView"> + <style name="NumPadKey" parent="Theme.SystemUI"> + <item name="android:colorControlNormal">?android:attr/colorBackground</item> + <item name="android:colorControlHighlight">?android:attr/colorAccent</item> + <item name="android:background">@drawable/num_pad_key_background</item> + </style> + <style name="Widget.TextView.NumPadKey.Digit" + parent="@android:style/Widget.DeviceDefault.TextView"> <item name="android:singleLine">true</item> <item name="android:gravity">center_horizontal|center_vertical</item> <item name="android:background">@null</item> @@ -38,26 +44,25 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:paddingBottom">-16dp</item> - <item name="android:colorControlHighlight">?android:attr/textColorPrimary</item> </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="Keyguard.ImageButton.NumPadDelete" parent="@android:style/Widget.DeviceDefault.ImageButton"> + <style name="NumPadKey.Delete"> <item name="android:src">@drawable/ic_backspace_black_24dp</item> - <item name="android:paddingBottom">11sp</item> <item name="android:tint">?android:attr/textColorSecondary</item> <item name="android:tintMode">src_in</item> - <item name="android:src">@drawable/ic_backspace_black_24dp</item> </style> - <style name="Keyguard.ImageButton.NumPadEnter" parent="@android:style/Widget.DeviceDefault.ImageButton"> - <item name="android:src">@drawable/ic_keyboard_tab_36dp</item> - <item name="android:paddingBottom">11sp</item> + <style name="NumPadKey.Enter"> + <item name="android:colorControlNormal">?android:attr/textColorSecondary</item> + <item name="android:src">@drawable/ic_keyboard_tab_36dp</item> </style> - <style name="Widget.TextView.NumPadKey.Klondike"> + <style name="Widget.TextView.NumPadKey.Klondike" + parent="@android:style/Widget.DeviceDefault.TextView"> <item name="android:textSize">12sp</item> + <item name="android:background">@null</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:paddingBottom">0dp</item> diff --git a/packages/SystemUI/res-keyguard/drawable/pin_divider.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml index 39104b575ecd..0a3afc575c4a 100644 --- a/packages/SystemUI/res-keyguard/drawable/pin_divider.xml +++ b/packages/SystemUI/res/drawable/qs_background_primary.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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. @@ -12,9 +12,11 @@ ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and - ~ limitations under the License + ~ limitations under the License. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/pin_divider_color" /> -</shape>
\ No newline at end of file +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape> + <solid android:color="?android:attr/colorBackground"/> + <corners android:radius="@dimen/notification_corner_radius" /> + </shape> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 387f2f2ccfc7..77f17439c487 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -25,7 +25,8 @@ <View android:id="@+id/quick_settings_background" android:layout_width="match_parent" - android:layout_height="0dp" /> + android:layout_height="0dp" + android:background="@drawable/qs_background_primary" /> <com.android.systemui.qs.NonInterceptingScrollView android:id="@+id/expanded_qs_scroll_view" diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index f9841005e8f7..1630244468e5 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -43,12 +43,6 @@ android:visibility="invisible" /> </com.android.systemui.statusbar.BackDropView> - <com.android.systemui.statusbar.LightRevealScrim - android:id="@+id/light_reveal_scrim" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" /> - <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" @@ -57,6 +51,12 @@ sysui:ignoreRightInset="true" /> + <com.android.systemui.statusbar.LightRevealScrim + android:id="@+id/light_reveal_scrim" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + <include layout="@layout/status_bar_expanded" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index ba39d1e38ac8..e70930aca5d1 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuwe gebruiker"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Vliegtuigveilig"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Netwerke is beskikbaar"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Netwerke is nie beskikbaar nie"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nie gekoppel nie"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Geen netwerk nie"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi af"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index a193bbbec03e..a5ca6e70e2d3 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"አዲስ ተጠቃሚ"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"በይነመረብ"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"አውሮፕላን-ደህንነቱ የተጠበቀ"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"አውታረ መረቦች ይገኛሉ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"አውታረ መረቦች አይገኙም"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"አልተገናኘም"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ምንም አውታረ መረብ የለም"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ጠፍቷል"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index a634281dceb7..97fcbd6daebe 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -362,12 +362,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"مستخدم جديد"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"الإنترنت"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"آمنة في الطائرة"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"الشبكات متوفرة"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"الشبكات غير متوفرة"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ليست متصلة"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"لا تتوفر شبكة"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"إيقاف Wi-Fi"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 1151644fd849..6ad6b0883ec0 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"নতুন ব্যৱহাৰকাৰী"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ৱাই-ফাই"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ইণ্টাৰনেট"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"এয়াৰপ্লে’নত ব্যৱহাৰৰ বাবে সুৰক্ষিত"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"নেটৱৰ্ক উপলব্ধ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"নেটৱৰ্ক উপলব্ধ নহয়"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"সংযোগ হৈ থকা নাই"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"নেটৱৰ্ক নাই"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ৱাই-ফাই অফ"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 146add2f5319..a9441db216b2 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Yeni istifadəçi"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"İnternet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Təyyarə üçün güvənli şəbəkə"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Şəbəkələr əlçatandır"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Şəbəkələr əlçatan deyil"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Bağlantı yoxdur"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Şəbəkə yoxdur"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi sönülüdür"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 77b101c392ed..aebd1e25d014 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -359,12 +359,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Bezbedno za avion"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Mreže su dostupne"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Mreže nisu dostupne"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Veza nije uspostavljena"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nema mreže"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi je isključen"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 5977d043eb32..f115f3f6bd6e 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новы карыстальнік"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Інтэрнэт"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Бяспечныя ў самалёце"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Сеткі даступныя"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Сеткі недаступныя"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Няма падключэння"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Няма сеткi"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi адключаны"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 53eeac81ef0d..ae94f5847ba9 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нов потребител"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Безопасно в самолети"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Налице са мрежи"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Няма достъпни мрежи"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Няма връзка"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Няма мрежа"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi е изключен"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 7717c653bc09..7ed6937ee4ba 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -359,7 +359,7 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Sigurno za rad u zrakoplovu"</string> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Sigurno za korištenje u avionu"</string> <string name="quick_settings_networks_available" msgid="1875138606855420438">"Mreže su dostupne"</string> <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Mreže nisu dostupne"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nije povezano"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index f648bc6a0c94..80c4d283d87c 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Usuari nou"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Mode d\'avió"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Xarxes disponibles"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Xarxes no disponibles"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Desconnectat"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No hi ha cap xarxa"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desconnectada"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 4d14d2b41386..e266b20382c2 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nový uživatel"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Použitelné v letadle"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Dostupné sítě"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Nedostupné sítě"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nepřipojeno"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Žádná síť"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi vypnuta"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 95b1a55206c0..ba8b1e8bdba0 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny bruger"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Kan bruges i fly"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Tilgængelige netværk"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Ingen tilgængelige netværk"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ikke forbundet"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Intet netværk"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi slået fra"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index e6acd87f6cb3..7b69260eb246 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Neuer Nutzer"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WLAN"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Flugsicher"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Netzwerke verfügbar"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Netzwerke nicht verfügbar"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nicht verbunden"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Kein Netz"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WLAN aus"</string> @@ -938,7 +935,7 @@ <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string> <string name="wifi_is_off" msgid="5389597396308001471">"WLAN ist deaktiviert"</string> <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth ist deaktiviert"</string> - <string name="dnd_is_off" msgid="3185706903793094463">"\"Bitte nicht stören\" ist deaktiviert"</string> + <string name="dnd_is_off" msgid="3185706903793094463">"„Bitte nicht stören“ ist deaktiviert"</string> <string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"\"Bitte nicht stören\" wurde von einer automatischen Regel aktiviert (<xliff:g id="ID_1">%s</xliff:g>)."</string> <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"\"Bitte nicht stören\" wurde von einer App aktiviert (<xliff:g id="ID_1">%s</xliff:g>)."</string> <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"\"Bitte nicht stören\" wurde von einer automatischen Regel oder einer App aktiviert."</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 185af57d12cd..1fa6b11c7928 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Input Method"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Location Off"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Block camera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mute microphone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media device"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Emergency Calls Only"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Aeroplane-safe"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Networks available"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Networks unavailable"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 3517f878d512..1c1bd89e2826 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Input Method"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Location Off"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Block camera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mute microphone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media device"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Emergency Calls Only"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Aeroplane-safe"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Networks available"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Networks unavailable"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 185af57d12cd..1fa6b11c7928 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Input Method"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Location Off"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Block camera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mute microphone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media device"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Emergency Calls Only"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Aeroplane-safe"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Networks available"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Networks unavailable"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 185af57d12cd..1fa6b11c7928 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Input Method"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Location Off"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Block camera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mute microphone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media device"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Emergency Calls Only"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Aeroplane-safe"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Networks available"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Networks unavailable"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 306116cf3e36..791d75a7dc91 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Input Method"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Location Off"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Block Camera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mute Microphone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media device"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Emergency Calls Only"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 27c424bdc6e9..9d7a4beaa792 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Método de introducción"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Ubicación desactivada"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Bloquear cámara"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Silenciar micrófono"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Dispositivo multimedia"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Solo emergencia"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Usuario nuevo"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Seguro para aviones"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Redes disponibles"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes no disponible"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Sin conexión"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Sin red"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desactivada"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index ab5d0d2440d2..fcec044250be 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuevo usuario"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Modo avión"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Redes disponibles"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes no disponibles"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"No conectado"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No hay red."</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desactivado"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 6c7e19a2a6ae..f8745ee20c8c 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Uus kasutaja"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Lennukikindel"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Võrgud on saadaval"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Võrgud pole saadaval"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ühendus puudub"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Võrku pole"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi-ühendus on väljas"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index da7733c59632..d6569f89e3ab 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Erabiltzaile berria"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifia"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Hegaldietarako segurua"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Erabilgarri daude sareak"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Ez dago sarerik erabilgarri"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Konektatu gabe"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ez dago sarerik"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi konexioa desaktibatuta"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 28708b945500..c186b6e9c743 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Uusi käyttäjä"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Lentokoneturvallinen"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Verkkoja käytettävissä"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Ei verkkoja käytettävissä"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ei yhteyttä"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ei verkkoa"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi-yhteys pois käytöstä"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 50a3cfbf442a..5b55fbde9414 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nouvel utilisateur"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Acceptés dans les avions"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Réseaux disponibles"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Réseaux non disponibles"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non connecté"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Aucun réseau"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi désactivé"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 4c6a53ebc589..70505f866a8b 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novo usuario"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Redes seguras para os avións"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Redes dispoñibles"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Non hai redes dispoñibles"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non conectada"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Non hai rede"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wifi desactivada"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index add1ef524811..11c35db0f310 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"નવો વપરાશકર્તા"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"વાઇ-ફાઇ"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ઇન્ટરનેટ"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"એરપ્લેન મોડમાં ઉપયોગ માટે સુરક્ષિત"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"નેટવર્ક ઉપલબ્ધ છે"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"નેટવર્ક અનુપલબ્ધ છે"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"કનેક્ટ થયેલ નથી"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"કોઈ નેટવર્ક નથી"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"વાઇ-ફાઇ બંધ"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index b4d61bbe6629..0a34f6f9ba74 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"नया उपयोगकर्ता"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"वाई-फ़ाई"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"इंटरनेट"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"हवाई जहाज़ मोड पर, काम करने वाले सुरक्षित नेटवर्क"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"नेटवर्क उपलब्ध हैं"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"नेटवर्क उपलब्ध नहीं हैं"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"कनेक्ट नहीं है"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"कोई नेटवर्क नहीं"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"वाई-फ़ाई बंद"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 958e25f696d9..f62de80aace3 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Új felhasználó"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Repülőgépen használható"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Használhatók hálózatok"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Nem használhatók hálózatok"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nincs kapcsolat"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nincs hálózat"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi kikapcsolva"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 4fd1abe78fc4..49f3eb254e18 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -358,7 +358,7 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Նոր օգտատեր"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Ինտերնետ"</string> - <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Ցանցեր, որոնք անվտանգ են ինքնաթիռում"</string> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Ինքնաթիռում անվտանգ"</string> <string name="quick_settings_networks_available" msgid="1875138606855420438">"Հասանելի ցանցեր"</string> <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Անհասանելի ցանցեր"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Միացված չէ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 4136ead36ee9..29b979787f4a 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Pengguna baru"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Aman di pesawat"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Jaringan tersedia"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Jaringan tidak tersedia"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Tidak Terhubung"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tidak Ada Jaringan"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Mati"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 15c07e428ca5..9cd237925d9b 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nýr notandi"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Öruggt í flugi"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Net í boði"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Net er ekki tiltækt"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Engin tenging"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ekkert net"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Slökkt á Wi-Fi"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 299824b1aa7e..b08406f81220 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuovo utente"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Utilizzabili in aereo"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Reti disponibili"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Reti non disponibili"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non connessa"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nessuna rete"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi disattivato"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index e8cc8bfcd7bc..368d9eee1de2 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"משתמש חדש"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"אינטרנט"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"רשתות בטוחות לטיסה"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"רשתות זמינות"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"אין רשתות זמינות"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"אין חיבור"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"אין רשת"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi כבוי"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 459b52037ff1..c3dfec955060 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"新しいユーザー"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"インターネット"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"機内モードで利用可能"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ネットワークが利用できます"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ネットワークは利用できません"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"接続されていません"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ネットワークなし"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi OFF"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 65f303283fe1..205ec7608de0 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Жаңа пайдаланушы"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Ұшақта пайдалануға болатын"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Желілер қолжетімді."</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Желілер қолжетімді емес."</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Жалғанбаған"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Желі жоқ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi өшірулі"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index a4f6e0a3b801..1a4b525e4896 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"អ្នកប្រើថ្មី"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"អ៊ីនធឺណិត"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"សុវត្ថិភាពពេលជិះយន្តហោះ"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"បណ្ដាញដែលអាចប្រើបាន"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"មិនអាចប្រើបណ្តាញបានទេ"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"មិនបានតភ្ជាប់"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"គ្មានបណ្ដាញ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"វ៉ាយហ្វាយបានបិទ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index e02ed5a44ffb..db5b31991df2 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"ಹೊಸ ಬಳಕೆದಾರರು"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ವೈ-ಫೈ"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ಇಂಟರ್ನೆಟ್"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿ ಬಳಸಲು ಸುರಕ್ಷಿತವಾಗಿದೆ"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ನೆಟ್ವರ್ಕ್ಗಳು ಲಭ್ಯವಿವೆ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ನೆಟ್ವರ್ಕ್ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ನೆಟ್ವರ್ಕ್ ಇಲ್ಲ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ವೈ-ಫೈ ಆಫ್"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index e9d33fa70d72..9c0b27cb2f68 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"신규 사용자"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"인터넷"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"항공 안전"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"네트워크 사용 가능"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"네트워크 사용 불가"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"연결되어 있지 않음"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"네트워크가 연결되지 않음"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 꺼짐"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index f4191d4b9add..b7ee71f9b105 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Жаңы колдонуучу"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Учак режимине ылайыктуу"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Тармактар жеткиликтүү"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Тармактар жеткиликсиз"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Байланышкан жок"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Желе жок"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi өчүк"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 8d9051de4213..74e1ef775189 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Naujas naudotojas"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internetas"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Saugu naudotis lėktuvuose"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Tinklai pasiekiami"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Tinklai nepasiekiami"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Neprisijungta"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tinklo nėra"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"„Wi-Fi“ išjungta"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 1cde5323ef6c..1de72c570843 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -359,12 +359,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Jauns lietotājs"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internets"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Var izmantot lidojuma režīmā"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Tīkli ir pieejami"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Tīkli nav pieejami"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nav izveidots savienojums"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nav tīkla"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ir izslēgts"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index a006bc97557b..f17e09625669 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нов корисник"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Безбедно за во авион"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Мрежите се достапни"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Мрежите се недостапни"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Не е поврзано"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нема мрежа"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi е исклучено"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 4d2898fc131f..996562e59ed2 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"പുതിയ ഉപയോക്താവ്"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"വൈഫൈ"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ഇന്റർനെറ്റ്"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"വിമാന-സുരക്ഷിതം"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"നെറ്റ്വർക്കുകൾ ലഭ്യമാണ്"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"നെറ്റ്വർക്കുകൾ ലഭ്യമല്ല"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"കണക്റ്റ് ചെയ്തിട്ടില്ല"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"നെറ്റ്വർക്ക് ഒന്നുമില്ല"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"വൈഫൈ ഓഫുചെയ്യുക"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 8845b0722292..efbbb808158c 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Шинэ хэрэглэгч"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернэт"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Аюулгүй нислэг"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Сүлжээ боломжтой"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Сүлжээ боломжгүй"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Холбогдоогүй"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Сүлжээгүй"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi унтарсан"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 48498b7ae82d..b2041dfeef33 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"नवीन वापरकर्ता"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"वाय-फाय"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"इंटरनेट"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"विमानासाठी सुरक्षित"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"नेटवर्क उपलब्ध आहेत"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"नेटवर्क उपलब्ध नाहीत"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"कनेक्ट केले नाही"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"नेटवर्क नाही"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"वाय-फाय बंद"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 42044175c9e3..db7d3ce99c92 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Pengguna baharu"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Selamat pesawat"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Rangkaian tersedia"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Rangkaian tidak tersedia"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Tidak Disambungkan"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tiada Rangkaian"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Dimatikan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 9b7488e1efd3..4dfdcf37112a 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"အသုံးပြုသူ အသစ်"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"အင်တာနက်"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"လေယာဉ်ပျံလုံခြုံရေး"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ကွန်ရက်များ ရနိုင်သည်"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ကွန်ရက်များ မရနိုင်ပါ"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ချိတ်ဆက်မထားပါ"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ကွန်ရက်မရှိပါ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ဝိုင်ဖိုင်ပိတ်ရန်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 490ba99e540e..398631e2dd43 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny bruker"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internett"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Trygg på fly"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Nettverk er tilgjengelige"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Nettverk er utilgjengelige"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ikke tilkoblet"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ingen nettverk"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi er av"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index cf267fd98ee9..6c70a7f6a774 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"नयाँ प्रयोगकर्ता"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"इन्टरनेट"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"हवाइजहाज मोडमा काम गर्ने सुरक्षित नेटवर्क"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"उपलब्ध नेटवर्कहरू"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"नेटवर्क उपलब्ध छैन"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"जोडिएको छैन"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"नेटवर्क छैन"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi बन्द"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index ddb9abc35b7e..b9a258322681 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nieuwe gebruiker"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Geschikt voor vliegtuigen"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Netwerken beschikbaar"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Netwerken niet beschikbaar"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Niet verbonden"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Geen netwerk"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wifi uit"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index ce6723e80965..40d5c3ef6581 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ୱାଇ-ଫାଇ"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ଇଣ୍ଟରନେଟ୍"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ଏୟାରପ୍ଲେନ୍-ସେଫ୍"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ନେଟୱାର୍କଗୁଡ଼ିକ ଉପଲବ୍ଧ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ସଂଯୁକ୍ତ ହୋଇନାହିଁ"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ନେଟ୍ୱର୍କ ନାହିଁ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ୱାଇ-ଫାଇ ଅଫ୍"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index ad40d6ca8fac..4aece95b86e1 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ਵਾਈ-ਫਾਈ"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ਇੰਟਰਨੈੱਟ"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ਹਵਾਈ-ਜਹਾਜ਼ ਸੁਰੱਖਿਅਤ ਮੋਡ"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਹਨ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹਨ"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ਕੋਈ ਨੈੱਟਵਰਕ ਨਹੀਂ"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ਵਾਈ-ਫਾਈ ਬੰਦ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 75abfdace58e..fbbbf11a62d8 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nowy użytkownik"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Bezpieczne w trybie samolotowym"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Sieci dostępne"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Sieci niedostępne"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Brak połączenia"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Brak sieci"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi wyłączone"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 1ab1002cbba4..a29cf03097d2 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Método de entrada"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Localização desativada"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Bloquear câmera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Desativar microfone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Dispositivo de mídia"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Chamadas de emergência"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 06b67fb4b367..478d1ee74e85 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Método de Introdução"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Localização Desativada"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Bloquear a câmara"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Desativar o som do microfone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Dispositivo multimédia"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Apenas chamadas de emergência"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 1ab1002cbba4..a29cf03097d2 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Método de entrada"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Localização desativada"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Bloquear câmera"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Desativar microfone"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Dispositivo de mídia"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Chamadas de emergência"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 54d53af19835..f78a95a3c5c3 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -359,12 +359,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Utilizator nou"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Sigur pentru avion"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Sunt disponibile rețele"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Nu sunt disponibile rețele"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Neconectată"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nicio rețea"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi deconectat"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index b6c2812c81ba..2ae6f0d23d09 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новый пользователь"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Безопасные в самолете"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Сети доступны"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Сети недоступны"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Нет соединения"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нет сети"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi выкл."</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index c1ba12bcda76..fe92a8d3d8d3 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"ආදාන ක්රමය"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"ස්ථානය"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"ස්ථානය අක්රියයි"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"කැමරාව අවහිර කරන්න"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"මයික්රෆෝනය නිහඬ කරන්න"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"මාධ්ය උපාංගය"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"හදිසි ඇමතුම් පමණි"</string> @@ -358,12 +356,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"නව පරිශීලකයා"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"අන්තර්ජාලය"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ගුවන් යානා-ආරක්ෂිත"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"ජාල තිබේ"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ජාල නොමැත"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"සම්බන්ධ වී නොමැත"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ජාලයක් නැත"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi අක්රියයි"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index dfa371ba15d5..9aeaef81d903 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nový používateľ"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi‑Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Bezpečné v lietadle"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Siete sú k dispozícii"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Siete nie sú k dispozícii"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nepripojené"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Žiadna sieť"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Sieť Wi‑Fi je vypnutá"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 5f5ee6526480..e27c03da86b9 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nov uporabnik"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Varna uporaba v letalu"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Omrežja so na voljo"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Omrežja niso na voljo"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Povezava ni vzpostavljena"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ni omrežja"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi izklopljen"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index e1ca27471098..a5a7de70b3c9 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Përdorues i ri"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Të sigurta për në aeroplan"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Ofrohen rrjete"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Rrjetet nuk ofrohen"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nuk është i lidhur"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nuk ka rrjet"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi është i çaktivizuar"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index d7bc5a1b1776..7f7c48354422 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -359,12 +359,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нови корисник"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Безбедно за авион"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Мреже су доступне"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Мреже нису доступне"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Веза није успостављена"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нема мреже"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi је искључен"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 3e38bdd4d93d..577719dee47d 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny användare"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Flygplanssäker"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Nätverk är tillgängliga"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Inga nätverk är tillgängliga"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ej ansluten"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Inget nätverk"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi av"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index ebb7fa64aca5..ec45b066f207 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"புதியவர்"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"வைஃபை"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"இணையம்"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"விமானப் பாதுகாப்பு நெட்வொர்க்குகள்"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"நெட்வொர்க்குகள் கிடைக்கின்றன"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"நெட்வொர்க்குகள் கிடைக்கவில்லை"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"இணைக்கப்படவில்லை"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"நெட்வொர்க் இல்லை"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"வைஃபையை முடக்கு"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 1456a8c586c1..a62accb0ba4d 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -52,7 +52,7 @@ <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ని నిర్వహించడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ని తెరవాలా?"</string> <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ని హ్యాండిల్ చేయడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ను తెరవాలా?\nఈ యాప్కు రికార్డ్ చేసే అనుమతి మంజూరు కాలేదు, అయినా ఈ USB పరికరం ద్వారా ఆడియోను క్యాప్చర్ చేయగలదు."</string> <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"<xliff:g id="USB_ACCESSORY">%2$s</xliff:g>ని నిర్వహించడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ని తెరవాలా?"</string> - <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"ఈ USB ఉపకరణంతో ఇన్స్టాల్ చేయబడిన అనువర్తనాలు ఏవీ పని చేయవు. ఈ ఉపకరణం గురించి <xliff:g id="URL">%1$s</xliff:g>లో మరింత తెలుసుకోండి"</string> + <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"ఈ USB ఉపకరణంతో ఇన్స్టాల్ చేయబడిన యాప్లు ఏవీ పని చేయవు. ఈ ఉపకరణం గురించి <xliff:g id="URL">%1$s</xliff:g>లో మరింత తెలుసుకోండి"</string> <string name="title_usb_accessory" msgid="1236358027511638648">"USB ఉపకరణం"</string> <string name="label_view" msgid="6815442985276363364">"వీక్షించండి"</string> <string name="always_use_device" msgid="210535878779644679">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> కనెక్ట్ అయి ఉన్న ఎల్లప్పుడూ <xliff:g id="APPLICATION">%1$s</xliff:g>ని తెరవండి"</string> @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"కొత్త వినియోగదారు"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"ఇంటర్నెట్"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"విమాన-సురక్షితం"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"నెట్వర్క్లు అందుబాటులో ఉన్నాయి"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"నెట్వర్క్లు అందుబాటులో లేవు"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"కనెక్ట్ చేయబడలేదు"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"నెట్వర్క్ లేదు"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ఆఫ్లో ఉంది"</string> @@ -466,14 +463,14 @@ <string name="user_add_user" msgid="4336657383006913022">"వినియోగదారుని జోడించండి"</string> <string name="user_new_user_name" msgid="2019166282704195789">"కొత్త వినియోగదారు"</string> <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"గెస్ట్ సెషన్ను ముగించాలా?"</string> - <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని అనువర్తనాలు మరియు డేటా తొలగించబడతాయి."</string> + <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string> <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"సెషన్ను ముగించు"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"పునఃస్వాగతం, అతిథి!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"మీరు మీ సెషన్ని కొనసాగించాలనుకుంటున్నారా?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"మొదటి నుండి ప్రారంభించు"</string> <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"అవును, కొనసాగించు"</string> <string name="guest_notification_title" msgid="4434456703930764167">"అతిథి వినియోగదారు"</string> - <string name="guest_notification_text" msgid="4202692942089571351">"అనువర్తనాలు, డేటా తొలగించేందుకు అతిథి వినియోగదారు తీసివేయండి"</string> + <string name="guest_notification_text" msgid="4202692942089571351">"యాప్లు, డేటా తొలగించేందుకు అతిథి వినియోగదారు తీసివేయండి"</string> <string name="guest_notification_remove_action" msgid="4153019027696868099">"అతిథిని తీసివేయి"</string> <string name="user_logout_notification_title" msgid="3644848998053832589">"వినియోగదారుని లాగ్ అవుట్ చేయండి"</string> <string name="user_logout_notification_text" msgid="7441286737342997991">"ప్రస్తుత వినియోగదారును లాగ్ అవుట్ చేయండి"</string> @@ -486,7 +483,7 @@ <item quantity="one">ఒక్క వినియోగదారుని మాత్రమే సృష్టించవచ్చు.</item> </plurals> <string name="user_remove_user_title" msgid="9124124694835811874">"వినియోగదారుని తీసివేయాలా?"</string> - <string name="user_remove_user_message" msgid="6702834122128031833">"ఈ వినియోగదారుకు సంబంధించిన అన్ని అనువర్తనాలు మరియు డేటా తొలగించబడతాయి."</string> + <string name="user_remove_user_message" msgid="6702834122128031833">"ఈ వినియోగదారుకు సంబంధించిన అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string> <string name="user_remove_user_remove" msgid="8387386066949061256">"తీసివేయి"</string> <string name="battery_saver_notification_title" msgid="8419266546034372562">"బ్యాటరీ సేవర్ ఆన్లో ఉంది"</string> <string name="battery_saver_notification_text" msgid="2617841636449016951">"పనితీరుని మరియు నేపథ్య డేటాను తగ్గిస్తుంది"</string> @@ -542,30 +539,30 @@ <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"మీ కార్యాలయ ప్రొఫైల్లో మీ సంస్థ ఒక ప్రమాణపత్ర అధికారాన్ని ఇన్స్టాల్ చేసింది. మీ సురక్షిత నెట్వర్క్ ట్రాఫిక్ పర్యవేక్షించబడవచ్చు లేదా సవరించబడవచ్చు."</string> <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"ఈ పరికరంలో ప్రమాణపత్ర అధికారం ఇన్స్టాల్ చేయబడింది. మీ సురక్షిత నెట్వర్క్ ట్రాఫిక్ పర్యవేక్షించబడవచ్చు లేదా సవరించబడవచ్చు."</string> <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"మీ నిర్వాహకులు మీ పరికరంలోని ట్రాఫిక్ని పర్యవేక్షించగల నెట్వర్క్ లాగింగ్ని ఆన్ చేసారు."</string> - <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"మీరు <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> - <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"మీరు ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP_0">%1$s</xliff:g> మరియు <xliff:g id="VPN_APP_1">%2$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు."</string> - <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"మీ కార్యాలయ ప్రొఫైల్ ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> - <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"మీ వ్యక్తిగత ప్రొఫైల్ ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> + <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"మీరు <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> + <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"మీరు ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP_0">%1$s</xliff:g> మరియు <xliff:g id="VPN_APP_1">%2$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు."</string> + <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"మీ కార్యాలయ ప్రొఫైల్ ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> + <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"మీ వ్యక్తిగత ప్రొఫైల్ ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> <string name="monitoring_description_do_header_generic" msgid="6130190408164834986">"మీ పరికరం <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ద్వారా నిర్వహించబడుతోంది."</string> <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> మీ పరికరాన్ని నిర్వహించడానికి <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>ని ఉపయోగిస్తుంది."</string> <string name="monitoring_description_do_body" msgid="7700878065625769970">"మీ పరికరంతో అనుబంధించబడిన సెట్టింగ్లు, కార్పొరేట్ యాక్సెస్, యాప్లు, డేటా మరియు మీ పరికరం యొక్క స్థాన సమాచారాన్ని మీ నిర్వాహకులు పర్యవేక్షించగలరు మరియు నిర్వహించగలరు."</string> <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string> <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"మరింత తెలుసుకోండి"</string> - <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"మీరు <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> + <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"మీరు <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string> <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"VPN సెట్టింగ్లను తెరవండి"</string> <string name="monitoring_description_ca_cert_settings_separator" msgid="7107390013344435439">" "</string> <string name="monitoring_description_ca_cert_settings" msgid="8329781950135541003">"విశ్వసనీయ ఆధారాలను తెరువు"</string> <string name="monitoring_description_network_logging" msgid="577305979174002252">"మీ నిర్వాహకులు మీ పరికరంలోని ట్రాఫిక్ని పర్యవేక్షించగల నెట్వర్క్ లాగింగ్ని ఆన్ చేసారు.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి."</string> <string name="monitoring_description_vpn" msgid="1685428000684586870">"మీరు VPN కనెక్షన్ సెటప్ చేయడానికి ఒక యాప్నకు అనుమతి ఇచ్చారు.\n\nఈ యాప్ ఇమెయిల్లు,యాప్లు మరియు వెబ్సైట్లతో సహా మీ డివైజ్ మరియు నెట్వర్క్ కార్యకలాపాన్ని పర్యవేక్షించగలదు."</string> - <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ద్వారా మీ కార్యాలయ ప్రొఫైల్ నిర్వహించబడుతోంది.\n\nఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల సామర్థ్యం మీ నిర్వాహకులకు ఉంది.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి.\n\nమీరు VPNకి కూడా కనెక్ట్ అయ్యారు, ఇది మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> + <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ద్వారా మీ కార్యాలయ ప్రొఫైల్ నిర్వహించబడుతోంది.\n\nఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల సామర్థ్యం మీ నిర్వాహకులకు ఉంది.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి.\n\nమీరు VPNకి కూడా కనెక్ట్ అయ్యారు, ఇది మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"ఈ పరికరాన్ని మీ తల్లి/తండ్రి మేనేజ్ చేస్తున్నారు. మీ తల్లి/తండ్రి, మీరు ఉపయోగించే యాప్లు, మీ లొకేషన్, అలాగే మీ పరికర వినియోగ వ్యవధి వంటి సమాచారాన్ని చూడగలరు, మేనేజ్ చేయగలరు."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> - <string name="monitoring_description_app" msgid="376868879287922929">"మీరు ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు."</string> - <string name="monitoring_description_app_personal" msgid="1970094872688265987">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> - <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> - <string name="monitoring_description_app_work" msgid="3713084153786663662">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమరింత సమాచారం కోసం, మీ నిర్వాహకులను సంప్రదించండి."</string> - <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>కి కూడా మీరు కనెక్ట్ చేయబడ్డారు."</string> + <string name="monitoring_description_app" msgid="376868879287922929">"మీరు ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు."</string> + <string name="monitoring_description_app_personal" msgid="1970094872688265987">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> + <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string> + <string name="monitoring_description_app_work" msgid="3713084153786663662">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమరింత సమాచారం కోసం, మీ నిర్వాహకులను సంప్రదించండి."</string> + <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, యాప్లు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>కి కూడా మీరు కనెక్ట్ చేయబడ్డారు."</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent ద్వారా అన్లాక్ చేయబడింది"</string> <string name="keyguard_indication_trust_disabled" msgid="6820793704816727918">"మీరు మాన్యువల్గా అన్లాక్ చేస్తే మినహా పరికరం లాక్ చేయబడి ఉంటుంది"</string> <string name="keyguard_indication_trust_unlocked_plugged_in" msgid="2323452175329362855">"<xliff:g id="KEYGUARD_INDICATION">%1$s</xliff:g>\n<xliff:g id="POWER_INDICATION">%2$s</xliff:g>"</string> @@ -898,7 +895,7 @@ <string name="tuner_lock_screen" msgid="2267383813241144544">"లాక్ స్క్రీన్"</string> <string name="thermal_shutdown_title" msgid="2702966892682930264">"వేడెక్కినందుకు ఫోన్ ఆఫ్ చేయబడింది"</string> <string name="thermal_shutdown_message" msgid="6142269839066172984">"మీ ఫోన్ ఇప్పుడు సాధారణంగా పని చేస్తోంది.\nమరింత సమాచారం కోసం ట్యాప్ చేయండి"</string> - <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"మీ ఫోన్ చాలా వేడిగా ఉంది, కనుక చల్లబర్చడానికి ఆఫ్ చేయబడింది. మీ ఫోన్ ఇప్పుడు సాధారణంగా పని చేస్తుంది.\n\nమీరు ఇలా చేస్తే మీ ఫోన్ చాలా వేడెక్కవచ్చు:\n • వనరు-ఆధారిత అనువర్తనాలు (గేమింగ్, వీడియో లేదా నావిగేషన్ వంటి అనువర్తనాలు) ఉపయోగించడం\n • పెద్ద ఫైల్లను డౌన్లోడ్ లేదా అప్లోడ్ చేయడం\n • అధిక ఉష్ణోగ్రతలలో మీ ఫోన్ని ఉపయోగించడం"</string> + <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"మీ ఫోన్ చాలా వేడిగా ఉంది, కనుక చల్లబర్చడానికి ఆఫ్ చేయబడింది. మీ ఫోన్ ఇప్పుడు సాధారణంగా పని చేస్తుంది.\n\nమీరు ఇలా చేస్తే మీ ఫోన్ చాలా వేడెక్కవచ్చు:\n • వనరు-ఆధారిత యాప్లు (గేమింగ్, వీడియో లేదా నావిగేషన్ వంటి యాప్లు) ఉపయోగించడం\n • పెద్ద ఫైల్లను డౌన్లోడ్ లేదా అప్లోడ్ చేయడం\n • అధిక ఉష్ణోగ్రతలలో మీ ఫోన్ని ఉపయోగించడం"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string> <string name="high_temp_title" msgid="2218333576838496100">"ఫోన్ వేడెక్కుతోంది"</string> <string name="high_temp_notif_message" msgid="1277346543068257549">"ఫోన్ను చల్లబరిచే క్రమంలో కొన్ని ఫీచర్లు పరిమితం చేయబడ్డాయి.\nమరింత సమాచారం కోసం ట్యాప్ చేయండి"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 65ee10645ed2..03bb4c452760 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"ผู้ใช้ใหม่"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"อินเทอร์เน็ต"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ใช้บนเครื่องบินได้อย่างปลอดภัย"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"เครือข่ายที่พร้อมใช้งาน"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ใช้งานเครือข่ายไม่ได้"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ไม่ได้เชื่อมต่อ"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ไม่มีเครือข่าย"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ปิด WiFi"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 9eab088946a7..f037fd5d7463 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Bagong user"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Ligtas gamitin sa eroplano"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Available ang mga network"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Hindi available ang mga network"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Hindi Nakakonekta"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Walang Network"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Naka-off ang Wi-Fi"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 921cce839d3d..ca4434bddfa0 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Yeni kullanıcı"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Kablosuz"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"İnternet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Uçakta kullanımı güvenli"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Kullanılabilir ağlar"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Kullanılamayan ağlar"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Bağlı Değil"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ağ yok"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Kablosuz Kapalı"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 66a8cfb0157c..4bbd0626ed3b 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -360,12 +360,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новий користувач"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Інтернет"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Безпечні в літаку"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Доступні мережі"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Недоступні мережі"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Не під’єднано."</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Немає мережі"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi вимкнено"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 3865bc04e959..28b28c428135 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"نیا صارف"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"انٹرنیٹ"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"ہوائی جہاز کیلئے محفوظ"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"نیٹ ورکس دستیاب ہیں"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"نیٹ ورکس دستیاب نہیں ہیں"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"مربوط نہیں ہے"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"کوئی نیٹ ورک نہیں ہے"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi آف ہے"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index d6061a6fd47c..315ed754aea0 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -344,10 +344,8 @@ <string name="quick_settings_ime_label" msgid="3351174938144332051">"Kiritish usuli"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string> <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Joylashuvni aniqlash xizmati yoqilmagan"</string> - <!-- no translation found for quick_settings_camera_label (1367149596242401934) --> - <skip /> - <!-- no translation found for quick_settings_mic_label (8245831073612564953) --> - <skip /> + <string name="quick_settings_camera_label" msgid="1367149596242401934">"Kamerani bloklash"</string> + <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mikrofonni oʻchirish"</string> <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media qurilma"</string> <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string> <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Favqulodda chaqiruvlar"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 23e72c15a0bb..f0d1a907e45f 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Người dùng mới"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"An toàn với máy bay"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Có mạng"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Không có mạng"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Chưa được kết nối"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Không có mạng nào"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Tắt Wi-Fi"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 55b524ea4cfc..a18a4262f619 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"新用户"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"WLAN"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"互联网"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"可在飞机上安全使用"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"有可用的网络"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"没有可用的网络"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未连接"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"无网络"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WLAN:关闭"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 701f32dee855..1268cedc1d21 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"新使用者"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"互聯網"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"飛行安全"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"有可用的網絡"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"網絡無法使用"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未連線"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"沒有網絡"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 關閉"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 4210ec1cf9ea..e11013f03828 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"新使用者"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"網際網路"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"飛航安全"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"有可用的網路"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"沒有網路"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未連線"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"沒有網路"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 已關閉"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 940fc19d13ad..b022acaa6a15 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -358,12 +358,9 @@ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"Umsebenzisi omusha"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"I-Wi-Fi"</string> <string name="quick_settings_internet_label" msgid="6603068555872455463">"I-inthanethi"</string> - <!-- no translation found for quick_settings_airplane_safe_label (2665758539772645899) --> - <skip /> - <!-- no translation found for quick_settings_networks_available (1875138606855420438) --> - <skip /> - <!-- no translation found for quick_settings_networks_unavailable (1167847013337940082) --> - <skip /> + <string name="quick_settings_airplane_safe_label" msgid="2665758539772645899">"Indiza ephephile"</string> + <string name="quick_settings_networks_available" msgid="1875138606855420438">"Amanethiwekhi ayatholakala"</string> + <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Amanethiwekhi awatholakali"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Akuxhunyiwe"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ayikho inethiwekhi"</string> <string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"I-Wi-Fi icimile"</string> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index db8bfec2bac5..de2db9867de9 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -20,6 +20,7 @@ <bool name="flag_notification_pipeline2">false</bool> <bool name="flag_notification_pipeline2_rendering">false</bool> + <bool name="flag_shade_is_opaque">true</bool> <!-- b/171917882 --> <bool name="flag_notification_twocolumn">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ac2e342b3c34..6e9c5dcde95b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -974,6 +974,11 @@ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> <string name="quick_settings_screen_record_stop">Stop</string> + <!--- Content of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=NONE] --> + <string name="sensor_privacy_start_use_mic_dialog_content">To continue, <b><xliff:g id="app" example="Gmail">%s</xliff:g></b> needs access to your device microphone.</string> + <!--- Content of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=NONE] --> + <string name="sensor_privacy_start_use_camera_dialog_content">To continue, <b><xliff:g id="app" example="Gmail">%s</xliff:g></b> needs access to your device’s camera.</string> + <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] --> <string name="media_seamless_remote_device">Device</string> @@ -1039,6 +1044,9 @@ <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] --> <string name="keyguard_retry">Swipe up to try again</string> + <!-- Message shown when notifying user to unlock in order to use NFC. [CHAR LIMIT=60] --> + <string name="require_unlock_for_nfc">Unlock to use NFC</string> + <!-- Text on keyguard screen and in Quick Settings footer indicating that the user's device belongs to their organization. [CHAR LIMIT=60] --> <string name="do_disclosure_generic">This device belongs to your organization</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d706ec224a47..db260ce1b7b4 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -319,6 +319,7 @@ <item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item> <item name="*android:lockPatternStyle">@style/LockPatternStyle</item> <item name="passwordStyle">@style/PasswordTheme</item> + <item name="numPadKeyStyle">@style/NumPadKey</item> <item name="backgroundProtectedStyle">@style/BackgroundProtectedStyle</item> <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> <item name="shadowRadius">@dimen/keyguard_shadow_radius</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 57a4dab5729c..388eeb6cd706 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -24,7 +24,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; import android.view.MotionEvent; -import android.window.TransitionFilter; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.model.Task; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 4ddfccb21c73..6a6b964c2a8f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -27,10 +27,8 @@ import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Rect; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.View; -import android.widget.ImageButton; import com.android.internal.widget.LockscreenCredential; import com.android.settingslib.Utils; @@ -42,10 +40,9 @@ import com.android.systemui.R; public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView { protected PasswordTextView mPasswordEntry; - private View mOkButton; - private ImageButton mDeleteButton; + private NumPadButton mOkButton; + private NumPadButton mDeleteButton; private NumPadKey[] mButtons = new NumPadKey[10]; - private View mDivider; public KeyguardPinBasedInputView(Context context) { this(context, null); @@ -152,7 +149,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView mDeleteButton = findViewById(R.id.delete_button); mDeleteButton.setVisibility(View.VISIBLE); - mDivider = findViewById(R.id.divider); mButtons[0] = findViewById(R.id.key0); mButtons[1] = findViewById(R.id.key1); @@ -181,12 +177,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary) .getDefaultColor(); mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor)); - mDivider.setBackground(getContext().getDrawable(R.drawable.pin_divider)); - ContextThemeWrapper themedContext = new ContextThemeWrapper(mContext, - R.style.Widget_TextView_NumPadKey); - mDeleteButton.setBackground(themedContext.getDrawable(R.drawable.ripple_drawable_pin)); - mOkButton.setBackground(themedContext.getDrawable(R.drawable.ripple_drawable_pin)); + mDeleteButton.reloadColors(); + mOkButton.reloadColors(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 826020c71159..973b49384c09 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -53,8 +54,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final NotificationIconAreaController mNotificationIconAreaController; + private final DozeParameters mDozeParameters; - private boolean mKeyguardStatusViewAnimating; + private boolean mKeyguardStatusViewVisibilityAnimating; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; @Inject @@ -65,7 +67,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, - NotificationIconAreaController notificationIconAreaController) { + NotificationIconAreaController notificationIconAreaController, + DozeParameters dozeParameters) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; @@ -73,6 +76,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; mNotificationIconAreaController = notificationIconAreaController; + mDozeParameters = dozeParameters; } @Override @@ -140,7 +144,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardStatusViewAnimating) { + if (!mKeyguardStatusViewVisibilityAnimating) { mView.setAlpha(alpha); } } @@ -194,8 +198,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Update position of the view with an optional animation */ public void updatePosition(int x, int y, float scale, boolean animate) { - PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES, - animate); + // We animate the status view visible/invisible using Y translation, so don't change it + // while the animation is running. + if (!mKeyguardStatusViewVisibilityAnimating) { + PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES, + animate); + } if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { // reset any prior movement @@ -223,10 +231,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV boolean goingToFullShade, int oldStatusBarState) { mView.animate().cancel(); - mKeyguardStatusViewAnimating = false; + mKeyguardStatusViewVisibilityAnimating = false; if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD && statusBarState != KEYGUARD) || goingToFullShade) { - mKeyguardStatusViewAnimating = true; + mKeyguardStatusViewVisibilityAnimating = true; mView.animate() .alpha(0f) .setStartDelay(0) @@ -242,7 +250,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { mView.setVisibility(View.VISIBLE); - mKeyguardStatusViewAnimating = true; + mKeyguardStatusViewVisibilityAnimating = true; mView.setAlpha(0f); mView.animate() .alpha(1f) @@ -252,7 +260,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { - mKeyguardStatusViewAnimating = true; + mKeyguardStatusViewVisibilityAnimating = true; mView.animate() .alpha(0) .translationYBy(-getHeight() * 0.05f) @@ -261,6 +269,22 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV .setStartDelay(0) .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable) .start(); + } else if (mDozeParameters.shouldControlUnlockedScreenOff()) { + mKeyguardStatusViewVisibilityAnimating = true; + + mView.setVisibility(View.VISIBLE); + mView.setAlpha(0f); + + float curTranslationY = mView.getTranslationY(); + mView.setTranslationY(curTranslationY - getHeight() * 0.1f); + mView.animate() + .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f)) + .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + .translationY(curTranslationY) + .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) + .start(); } else { mView.setVisibility(View.VISIBLE); mView.setAlpha(1f); @@ -367,17 +391,17 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV }; private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { - mKeyguardStatusViewAnimating = false; + mKeyguardStatusViewVisibilityAnimating = false; mView.setVisibility(View.INVISIBLE); }; private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { - mKeyguardStatusViewAnimating = false; + mKeyguardStatusViewVisibilityAnimating = false; mView.setVisibility(View.GONE); }; private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { - mKeyguardStatusViewAnimating = false; + mKeyguardStatusViewVisibilityAnimating = false; }; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index b43496cb55dc..e59769bb61e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -60,6 +60,7 @@ import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.nfc.NfcAdapter; import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; @@ -183,6 +184,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_KEYGUARD_GOING_AWAY = 342; private static final int MSG_LOCK_SCREEN_MODE = 343; private static final int MSG_TIME_FORMAT_UPDATE = 344; + private static final int MSG_REQUIRE_NFC_UNLOCK = 345; public static final int LOCK_SCREEN_MODE_NORMAL = 0; public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1; @@ -1259,6 +1261,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (ACTION_USER_REMOVED.equals(action)) { mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1), 0)); + } else if (NfcAdapter.ACTION_REQUIRE_UNLOCK_FOR_NFC.equals(action)) { + mHandler.sendEmptyMessage(MSG_REQUIRE_NFC_UNLOCK); } } }; @@ -1314,6 +1318,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onAuthenticationAcquired(int acquireInfo) { handleFingerprintAcquired(acquireInfo); } + + @Override + public void onUdfpsPointerDown(int sensorId) { + Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId); + } + + @Override + public void onUdfpsPointerUp(int sensorId) { + Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId); + } }; private final FaceManager.FaceDetectionCallback mFaceDetectionCallback @@ -1725,6 +1739,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab break; case MSG_TIME_FORMAT_UPDATE: handleTimeFormatUpdate((String) msg.obj); + case MSG_REQUIRE_NFC_UNLOCK: + handleRequireUnlockForNfc(); break; default: super.handleMessage(msg); @@ -1787,6 +1803,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab allUserFilter.addAction(ACTION_USER_UNLOCKED); allUserFilter.addAction(ACTION_USER_STOPPED); allUserFilter.addAction(ACTION_USER_REMOVED); + allUserFilter.addAction(NfcAdapter.ACTION_REQUIRE_UNLOCK_FOR_NFC); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter, mHandler, UserHandle.ALL); @@ -2690,6 +2707,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Handle {@link #MSG_REQUIRE_NFC_UNLOCK} + */ + private void handleRequireUnlockForNfc() { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRequireUnlockForNfc(); + } + } + } + + /** * Handle {@link #MSG_REPORT_EMERGENCY_CALL_ACTION} */ private void handleReportEmergencyCallAction() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 36617c239352..e561a5a84f24 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -328,4 +328,9 @@ public class KeyguardUpdateMonitorCallback { */ public void onLockScreenModeChanged(int mode) { } + /** + * Called when notifying user to unlock in order to use NFC. + */ + public void onRequireUnlockForNfc() { } + } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java new file mode 100644 index 000000000000..cdf98581e29b --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.GradientDrawable; +import android.view.ContextThemeWrapper; +import android.view.ViewGroup; + +import androidx.annotation.StyleRes; + +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +/** + * Provides background color and radius animations for key pad buttons. + */ +class NumPadAnimator { + private ValueAnimator mAnimator; + private GradientDrawable mBackground; + private int mMargin; + private int mNormalColor; + private int mHighlightColor; + private int mStyle; + + NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) { + mBackground = (GradientDrawable) background.mutate(); + mStyle = style; + + reloadColors(context); + + mMargin = context.getResources().getDimensionPixelSize(R.dimen.num_pad_key_margin); + + // Actual values will be updated later, usually during an onLayout() call + mAnimator = ValueAnimator.ofFloat(0f); + mAnimator.setDuration(250); + mAnimator.setInterpolator(Interpolators.LINEAR); + mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator anim) { + mBackground.setCornerRadius((float) anim.getAnimatedValue()); + mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor, + anim.getAnimatedFraction())); + } + }); + + } + + void updateMargin(ViewGroup.MarginLayoutParams lp) { + lp.setMargins(mMargin, mMargin, mMargin, mMargin); + } + + void onLayout(int height) { + float startRadius = height / 10f; + float endRadius = height / 2f; + mBackground.setCornerRadius(endRadius); + mAnimator.setFloatValues(startRadius, endRadius); + } + + void start() { + mAnimator.cancel(); + mAnimator.start(); + } + + /** + * Reload colors from resources. + **/ + void reloadColors(Context context) { + int[] customAttrs = {android.R.attr.colorControlNormal, + android.R.attr.colorControlHighlight}; + + ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); + TypedArray a = ctw.obtainStyledAttributes(customAttrs); + mNormalColor = a.getColor(0, 0); + mHighlightColor = a.getColor(1, 0); + a.recycle(); + + mBackground.setColor(mNormalColor); + } +} + diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java new file mode 100644 index 000000000000..372810677649 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.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.keyguard; + +import android.content.Context; +import android.graphics.drawable.GradientDrawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewGroup; + +/** + * Similar to the {@link NumPadKey}, but displays an image. + */ +public class NumPadButton extends AlphaOptimizedImageButton { + + private final NumPadAnimator mAnimator; + + public NumPadButton(Context context, AttributeSet attrs) { + super(context, attrs); + + mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(), + attrs.getStyleAttribute()); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams()); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Set width/height to the same value to ensure a smooth circle for the bg + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + mAnimator.onLayout(b - t); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mAnimator.start(); + return super.onTouchEvent(event); + } + + /** + * Reload colors from resources. + **/ + public void reloadColors() { + mAnimator.reloadColors(getContext()); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index a5182055e14d..8ebcb20fe63f 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -18,11 +18,10 @@ package com.android.keyguard; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.os.PowerManager; import android.os.SystemClock; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -48,6 +47,8 @@ public class NumPadKey extends ViewGroup { private int mTextViewResId; private PasswordTextView mTextView; + private final NumPadAnimator mAnimator; + private View.OnClickListener mListener = new View.OnClickListener() { @Override public void onClick(View thisView) { @@ -73,7 +74,7 @@ public class NumPadKey extends ViewGroup { } public NumPadKey(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs, R.attr.numPadKeyStyle); } public NumPadKey(Context context, AttributeSet attrs, int defStyle) { @@ -84,7 +85,8 @@ public class NumPadKey extends ViewGroup { super(context, attrs, defStyle); setFocusable(true); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumPadKey); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumPadKey, defStyle, + contentResource); try { mDigit = a.getInt(R.styleable.NumPadKey_digit, mDigit); @@ -116,20 +118,16 @@ public class NumPadKey extends ViewGroup { final int len = klondike.length(); if (len > 0) { mKlondikeText.setText(klondike); - } else { + } else if (mKlondikeText.getVisibility() != View.GONE) { mKlondikeText.setVisibility(View.INVISIBLE); } } } - a = context.obtainStyledAttributes(attrs, android.R.styleable.View); - if (!a.hasValueOrEmpty(android.R.styleable.View_background)) { - Drawable rippleDrawable = new ContextThemeWrapper(mContext, - R.style.Widget_TextView_NumPadKey).getDrawable(R.drawable.ripple_drawable_pin); - setBackground(rippleDrawable); - } - a.recycle(); setContentDescription(mDigitText.getText().toString()); + + mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(), + R.style.NumPadKey); } /** @@ -142,9 +140,8 @@ public class NumPadKey extends ViewGroup { .getDefaultColor(); mDigitText.setTextColor(textColor); mKlondikeText.setTextColor(klondikeColor); - Drawable rippleDrawable = new ContextThemeWrapper(mContext, - R.style.Widget_TextView_NumPadKey).getDrawable(R.drawable.ripple_drawable_pin); - setBackground(rippleDrawable); + + mAnimator.reloadColors(getContext()); } @Override @@ -152,13 +149,21 @@ public class NumPadKey extends ViewGroup { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); } + + mAnimator.start(); + return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams()); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); + + // Set width/height to the same value to ensure a smooth circle for the bg + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); } @Override @@ -176,6 +181,8 @@ public class NumPadKey extends ViewGroup { left = centerX - mKlondikeText.getMeasuredWidth() / 2; mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom); + + mAnimator.onLayout(b - t); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java deleted file mode 100644 index c0f8cae607ca..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui; - -import android.annotation.NonNull; -import android.app.Notification; -import android.os.Handler; -import android.os.Looper; -import android.util.ArraySet; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.statusbar.NotificationInteractionTracker; -import com.android.systemui.statusbar.NotificationLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.util.time.SystemClock; - -import javax.inject.Inject; - -/** - * Extends the lifetime of foreground notification services such that they show for at least - * five seconds - */ -public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender { - - private static final String TAG = "FGSLifetimeExtender"; - @VisibleForTesting - static final int MIN_FGS_TIME_MS = 5000; - - private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback; - private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>(); - private Handler mHandler = new Handler(Looper.getMainLooper()); - private final SystemClock mSystemClock; - private final NotificationInteractionTracker mInteractionTracker; - - @Inject - public ForegroundServiceLifetimeExtender( - NotificationInteractionTracker interactionTracker, - SystemClock systemClock) { - mSystemClock = systemClock; - mInteractionTracker = interactionTracker; - } - - @Override - public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) { - mNotificationSafeToRemoveCallback = callback; - } - - @Override - public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { - if ((entry.getSbn().getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) == 0) { - return false; - } - - // Entry has triggered a HUN or some other interruption, therefore it has been seen and the - // interrupter might be retaining it anyway. - if (entry.hasInterrupted()) { - return false; - } - - boolean hasInteracted = mInteractionTracker.hasUserInteractedWith(entry.getKey()); - long aliveTime = mSystemClock.uptimeMillis() - entry.getCreationTime(); - return aliveTime < MIN_FGS_TIME_MS && !hasInteracted; - } - - @Override - public boolean shouldExtendLifetimeForPendingNotification( - @NonNull NotificationEntry entry) { - return shouldExtendLifetime(entry); - } - - @Override - public void setShouldManageLifetime( - @NonNull NotificationEntry entry, boolean shouldManage) { - if (!shouldManage) { - mManagedEntries.remove(entry); - return; - } - - mManagedEntries.add(entry); - - Runnable r = () -> { - if (mManagedEntries.contains(entry)) { - mManagedEntries.remove(entry); - if (mNotificationSafeToRemoveCallback != null) { - mNotificationSafeToRemoveCallback.onSafeToRemove(entry.getKey()); - } - } - }; - long delayAmt = MIN_FGS_TIME_MS - - (mSystemClock.uptimeMillis() - entry.getCreationTime()); - mHandler.postDelayed(r, delayAmt); - } -} - diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index d6cb1142061d..04e26d36f0a5 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -50,7 +50,6 @@ public class ForegroundServiceNotificationListener { ForegroundServiceController foregroundServiceController, NotificationEntryManager notificationEntryManager, NotifPipeline notifPipeline, - ForegroundServiceLifetimeExtender fgsLifetimeExtender, SystemClock systemClock) { mContext = context; mForegroundServiceController = foregroundServiceController; @@ -78,7 +77,6 @@ public class ForegroundServiceNotificationListener { removeNotification(entry.getSbn()); } }); - mEntryManager.addNotificationLifetimeExtender(fgsLifetimeExtender); notifPipeline.addCollectionListener(new NotifCollectionListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index c2f80d4d2a5b..033a3555394e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -35,11 +35,12 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; -import android.util.TypedValue; import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; +import com.android.internal.graphics.ColorUtils; +import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -157,16 +158,16 @@ public class UdfpsView extends View implements DozeReceiver, @Override public void dozeTimeTick() { - updateAodPosition(); + updateAodPositionAndColor(); } @Override public void onDozeAmountChanged(float linear, float eased) { mInterpolatedDarkAmount = eased; - updateAodPosition(); + updateAodPositionAndColor(); } - private void updateAodPosition() { + private void updateAodPositionAndColor() { mBurnInOffsetX = MathUtils.lerp(0f, getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - mMaxBurnInOffsetX, @@ -175,6 +176,7 @@ public class UdfpsView extends View implements DozeReceiver, getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - 0.5f * mMaxBurnInOffsetY, mInterpolatedDarkAmount); + updateColor(); postInvalidate(); } @@ -231,19 +233,21 @@ public class UdfpsView extends View implements DozeReceiver, Log.v(TAG, "onAttachedToWindow"); // Retrieve the colors each time, since it depends on day/night mode - final TypedValue tv = new TypedValue(); - mContext.getTheme().resolveAttribute(R.attr.wallpaperTextColor, tv, true); - final int authIconColor = mContext.getResources() - .getColor(tv.resourceId, mContext.getTheme()); - final int enrollIconColor = mContext.getColor(R.color.udfps_enroll_icon); + updateColor(); + getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); + } + + private void updateColor() { if (mShowReason == IUdfpsOverlayController.REASON_AUTH) { - mFingerprintDrawable.setTint(authIconColor); + final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext, + com.android.systemui.R.attr.wallpaperTextColor); + final int ambientDisplayIconColor = Color.WHITE; + mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor, + ambientDisplayIconColor, mInterpolatedDarkAmount)); } else if (mShowReason == IUdfpsOverlayController.REASON_ENROLL) { - mFingerprintDrawable.setTint(enrollIconColor); + mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); } - - getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 0fdaae82e2d0..5c8c9f22d585 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -96,7 +96,9 @@ public class DozeUi implements DozeMachine.Part { */ private void updateAnimateScreenOff() { if (mCanAnimateTransition) { - final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing + final boolean controlScreenOff = + mDozeParameters.getAlwaysOn() + && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff()) && !mHost.isPowerSaveActive(); mDozeParameters.setControlScreenOffAnimation(controlScreenOff); mHost.setAnimateScreenOff(controlScreenOff); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java index f527775449bf..5019e65c7182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java @@ -18,6 +18,7 @@ package com.android.systemui.keyguard; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import com.android.systemui.dagger.SysUISingleton; @@ -53,6 +54,17 @@ public class KeyguardLifecyclesDispatcher { mHandler.obtainMessage(what).sendToTarget(); } + /** + * @param what Message to send. + * @param pmReason Reason this message was triggered - this should be a value from either + * {@link PowerManager.WakeReason} or {@link PowerManager.GoToSleepReason}. + */ + void dispatch(int what, int pmReason) { + final Message message = mHandler.obtainMessage(what); + message.arg1 = pmReason; + message.sendToTarget(); + } + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -70,13 +82,13 @@ public class KeyguardLifecyclesDispatcher { mScreenLifecycle.dispatchScreenTurnedOff(); break; case STARTED_WAKING_UP: - mWakefulnessLifecycle.dispatchStartedWakingUp(); + mWakefulnessLifecycle.dispatchStartedWakingUp(msg.arg1 /* pmReason */); break; case FINISHED_WAKING_UP: mWakefulnessLifecycle.dispatchFinishedWakingUp(); break; case STARTED_GOING_TO_SLEEP: - mWakefulnessLifecycle.dispatchStartedGoingToSleep(); + mWakefulnessLifecycle.dispatchStartedGoingToSleep(msg.arg1 /* pmReason */); break; case FINISHED_GOING_TO_SLEEP: mWakefulnessLifecycle.dispatchFinishedGoingToSleep(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index cb83656c3842..1b033e91b76b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -24,9 +24,11 @@ import android.os.Binder; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; +import android.os.PowerManager; import android.os.Process; import android.os.Trace; import android.util.Log; +import android.view.WindowManagerPolicyConstants; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardDrawnCallback; @@ -117,27 +119,32 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void onStartedGoingToSleep(int reason) { + public void onStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { checkPermission(); - mKeyguardViewMediator.onStartedGoingToSleep(reason); + mKeyguardViewMediator.onStartedGoingToSleep( + WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason)); mKeyguardLifecyclesDispatcher.dispatch( - KeyguardLifecyclesDispatcher.STARTED_GOING_TO_SLEEP); + KeyguardLifecyclesDispatcher.STARTED_GOING_TO_SLEEP, pmSleepReason); } @Override // Binder interface - public void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered) { + public void onFinishedGoingToSleep( + @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { checkPermission(); - mKeyguardViewMediator.onFinishedGoingToSleep(reason, cameraGestureTriggered); + mKeyguardViewMediator.onFinishedGoingToSleep( + WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason), + cameraGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.FINISHED_GOING_TO_SLEEP); } @Override // Binder interface - public void onStartedWakingUp() { + public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp"); checkPermission(); mKeyguardViewMediator.onStartedWakingUp(); - mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.STARTED_WAKING_UP); + mKeyguardLifecyclesDispatcher.dispatch( + KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index f79b9910fff4..e7326698e43e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -85,17 +85,17 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dumpable; -import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardService; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.BiometricUnlockController; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; @@ -149,7 +149,8 @@ import dagger.Lazy; * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI * thread of the keyguard. */ -public class KeyguardViewMediator extends SystemUI implements Dumpable { +public class KeyguardViewMediator extends SystemUI implements Dumpable, + StatusBarStateController.StateListener { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; @@ -223,6 +224,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { private boolean mBootSendUserPresent; private boolean mShuttingDown; private boolean mDozing; + private boolean mAnimatingScreenOff; private final FalsingCollector mFalsingCollector; /** High level access to the power manager for WakeLocks */ @@ -709,6 +711,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { }; private DeviceConfigProxy mDeviceConfig; + private DozeParameters mDozeParameters; /** * Injected constructor. See {@link KeyguardModule}. @@ -725,7 +728,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { TrustManager trustManager, DeviceConfigProxy deviceConfig, NavigationModeController navigationModeController, - KeyguardDisplayManager keyguardDisplayManager) { + KeyguardDisplayManager keyguardDisplayManager, + DozeParameters dozeParameters, + StatusBarStateController statusBarStateController) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -751,6 +756,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> { mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode); })); + mDozeParameters = dozeParameters; + statusBarStateController.addCallback(this); } public void userActivity() { @@ -863,11 +870,11 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { /** * Called to let us know the screen was turned off. - * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or - * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. + * @param offReason either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. */ - public void onStartedGoingToSleep(int why) { - if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")"); + public void onStartedGoingToSleep(@WindowManagerPolicyConstants.OffReason int offReason) { + if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + offReason + ")"); synchronized (this) { mDeviceInteractive = false; mGoingToSleep = true; @@ -900,8 +907,11 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { } } else if (mShowing) { mPendingReset = true; - } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0) - || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) { + } else if ( + (offReason == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT + && timeout > 0) + || (offReason == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER + && !lockImmediately)) { doKeyguardLaterLocked(timeout); mLockLater = true; } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) { @@ -912,16 +922,23 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { playSounds(true); } } - mUpdateMonitor.dispatchStartedGoingToSleep(why); + mUpdateMonitor.dispatchStartedGoingToSleep(offReason); notifyStartedGoingToSleep(); } - public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) { - if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + why + ")"); + /** + * Called to let us know the screen finished turning off. + * @param offReason either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or + * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}. + */ + public void onFinishedGoingToSleep( + @WindowManagerPolicyConstants.OffReason int offReason, boolean cameraGestureTriggered) { + if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + offReason + ")"); synchronized (this) { mDeviceInteractive = false; mGoingToSleep = false; mWakeAndUnlocking = false; + mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff(); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; @@ -957,7 +974,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { } } - mUpdateMonitor.dispatchFinishedGoingToSleep(why); + mUpdateMonitor.dispatchFinishedGoingToSleep(offReason); } private boolean isKeyguardServiceEnabled() { @@ -1074,6 +1091,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { // TODO: Rename all screen off/on references to interactive/sleeping synchronized (this) { mDeviceInteractive = true; + mAnimatingScreenOff = false; cancelDoKeyguardLaterLocked(); cancelDoKeyguardForChildProfilesLocked(); if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence); @@ -1287,6 +1305,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { return mHiding; } + public boolean isAnimatingScreenOff() { + return mAnimatingScreenOff; + } + /** * Handles SET_OCCLUDED message sent by setOccluded() */ @@ -2259,6 +2281,16 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { setShowingLocked(mShowing); } + @Override + public void onDozeAmountChanged(float linear, float interpolated) { + // If we were animating the screen off, and we've completed the doze animation (doze amount + // is 1f), then show the activity lock screen. + if (mAnimatingScreenOff && mDozing && linear == 1f) { + mAnimatingScreenOff = false; + setShowingLocked(mShowing); + } + } + /** * @param pulsing true when device temporarily wakes up to display an incoming notification. */ @@ -2289,7 +2321,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { mAodShowing = aodShowing; if (notifyDefaultDisplayCallbacks) { notifyDefaultDisplayCallbacks(showing); - updateActivityLockScreenState(showing, aodShowing); + + if (!showing || !mAnimatingScreenOff) { + // Update the activity lock screen state unless we're animating in the keyguard + // for a screen off animation. In that case, we want the activity to remain visible + // until the animation completes. setShowingLocked is called again when the + // animation ends, so the activity lock screen will be shown at that time. + updateActivityLockScreenState(showing, aodShowing); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index 5161deb0e4de..de00d50b6e36 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -17,6 +17,7 @@ package com.android.systemui.keyguard; import android.annotation.IntDef; +import android.os.PowerManager; import android.os.Trace; import com.android.systemui.Dumpable; @@ -51,6 +52,9 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe public static final int WAKEFULNESS_GOING_TO_SLEEP = 3; private int mWakefulness = WAKEFULNESS_ASLEEP; + private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN; + private @PowerManager.GoToSleepReason int mLastSleepReason = + PowerManager.GO_TO_SLEEP_REASON_MIN; @Inject public WakefulnessLifecycle() { @@ -60,11 +64,27 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe return mWakefulness; } - public void dispatchStartedWakingUp() { + /** + * Returns the most recent reason the device woke up. This is one of PowerManager.WAKE_REASON_*. + */ + public @PowerManager.WakeReason int getLastWakeReason() { + return mLastWakeReason; + } + + /** + * Returns the most recent reason the device went to sleep up. This is one of + * PowerManager.GO_TO_SLEEP_REASON_*. + */ + public @PowerManager.GoToSleepReason int getLastSleepReason() { + return mLastSleepReason; + } + + public void dispatchStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { if (getWakefulness() == WAKEFULNESS_WAKING) { return; } setWakefulness(WAKEFULNESS_WAKING); + mLastWakeReason = pmWakeReason; dispatch(Observer::onStartedWakingUp); } @@ -76,11 +96,12 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe dispatch(Observer::onFinishedWakingUp); } - public void dispatchStartedGoingToSleep() { + public void dispatchStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { if (getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP) { return; } setWakefulness(WAKEFULNESS_GOING_TO_SLEEP); + mLastSleepReason = pmSleepReason; dispatch(Observer::onStartedGoingToSleep); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 626abfcc85fd..76281d8c0f00 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -43,6 +43,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.DeviceConfigProxy; @@ -82,7 +83,9 @@ public class KeyguardModule { @UiBackground Executor uiBgExecutor, DeviceConfigProxy deviceConfig, NavigationModeController navigationModeController, - KeyguardDisplayManager keyguardDisplayManager) { + KeyguardDisplayManager keyguardDisplayManager, + DozeParameters dozeParameters, + StatusBarStateController statusBarStateController) { return new KeyguardViewMediator( context, falsingCollector, @@ -97,7 +100,9 @@ public class KeyguardModule { trustManager, deviceConfig, navigationModeController, - keyguardDisplayManager + keyguardDisplayManager, + dozeParameters, + statusBarStateController ); } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index b2c2aa309811..1e0451601e50 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -46,7 +46,7 @@ import javax.inject.Inject class PrivacyItemController @Inject constructor( private val appOpsController: AppOpsController, @Main uiExecutor: DelayableExecutor, - @Background private val bgExecutor: Executor, + @Background private val bgExecutor: DelayableExecutor, private val deviceConfigProxy: DeviceConfigProxy, private val userTracker: UserTracker, private val logger: PrivacyLogger, @@ -75,6 +75,7 @@ class PrivacyItemController @Inject constructor( private const val DEFAULT_ALL_INDICATORS = false private const val DEFAULT_MIC_CAMERA = true private const val DEFAULT_LOCATION = false + const val TIME_TO_HOLD_INDICATORS = 5000L } @VisibleForTesting @@ -87,9 +88,9 @@ class PrivacyItemController @Inject constructor( ALL_INDICATORS, DEFAULT_ALL_INDICATORS) } - // TODO(b/168209929) Remove hardcode private fun isMicCameraEnabled(): Boolean { - return true + return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + MIC_CAMERA, DEFAULT_MIC_CAMERA) } private fun isLocationEnabled(): Boolean { @@ -101,6 +102,8 @@ class PrivacyItemController @Inject constructor( private var listening = false private val callbacks = mutableListOf<WeakReference<Callback>>() private val internalUiExecutor = MyExecutor(uiExecutor) + private var holdingIndicators = false + private var holdIndicatorsCancelled: Runnable? = null private val notifyChanges = Runnable { val list = privacyList @@ -112,6 +115,11 @@ class PrivacyItemController @Inject constructor( uiExecutor.execute(notifyChanges) } + private val stopHoldingAndNotifyChanges = Runnable { + updatePrivacyList(true) + uiExecutor.execute(notifyChanges) + } + var allIndicatorsAvailable = isAllIndicatorsEnabled() private set var micCameraAvailable = isMicCameraEnabled() @@ -132,11 +140,12 @@ class PrivacyItemController @Inject constructor( DEFAULT_ALL_INDICATORS) callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) } } - // TODO(b/168209929) Uncomment -// if (properties.keyset.contains(MIC_CAMERA)) { -// micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA) -// callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) } -// } + + if (properties.keyset.contains(MIC_CAMERA)) { + micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA) + callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) } + } + if (properties.keyset.contains(LOCATION)) { locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION) callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) } @@ -193,6 +202,14 @@ class PrivacyItemController @Inject constructor( userTracker.addCallback(userTrackerCallback, bgExecutor) } + private fun setHoldTimer() { + holdIndicatorsCancelled?.run() + holdingIndicators = true + holdIndicatorsCancelled = bgExecutor.executeDelayed({ + stopHoldingAndNotifyChanges.run() + }, TIME_TO_HOLD_INDICATORS) + } + private fun update(updateUsers: Boolean) { bgExecutor.execute { if (updateUsers) { @@ -257,9 +274,14 @@ class PrivacyItemController @Inject constructor( removeCallback(WeakReference(callback)) } - private fun updatePrivacyList() { + private fun updatePrivacyList(stopHolding: Boolean = false) { if (!listening) { privacyList = emptyList() + if (holdingIndicators) { + holdIndicatorsCancelled?.run() + logger.cancelIndicatorsHold() + holdingIndicators = false + } return } val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter { @@ -267,9 +289,43 @@ class PrivacyItemController @Inject constructor( it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE || it.code == AppOpsManager.OP_PHONE_CALL_CAMERA }.mapNotNull { toPrivacyItem(it) }.distinct() - logger.logUpdatedPrivacyItemsList( - list.joinToString(separator = ", ", transform = PrivacyItem::toLog)) - privacyList = list + processNewList(list, stopHolding) + } + + /** + * The controller will only go from indicators to no indicators (and notify its listeners), if + * [TIME_TO_HOLD_INDICATORS] has passed since it received an empty list from [AppOpsController]. + * + * If holding the last list (in the [TIME_TO_HOLD_INDICATORS] period) and a new non-empty list + * is retrieved from [AppOpsController], it will stop holding and notify about the new list. + */ + private fun processNewList(list: List<PrivacyItem>, stopHolding: Boolean) { + if (list.isNotEmpty()) { + // The new elements is not empty, so regardless of whether we are holding or not, we + // clear the holding flag and cancel the delayed runnable. + if (holdingIndicators) { + holdIndicatorsCancelled?.run() + logger.cancelIndicatorsHold() + holdingIndicators = false + } + logger.logUpdatedPrivacyItemsList( + list.joinToString(separator = ", ", transform = PrivacyItem::toLog)) + privacyList = list + } else if (holdingIndicators && stopHolding) { + // We are holding indicators, received an empty list and were told to stop holding. + logger.finishIndicatorsHold() + logger.logUpdatedPrivacyItemsList("") + holdingIndicators = false + privacyList = list + } else if (holdingIndicators && !stopHolding) { + // Empty list while we are holding. Ignore + } else if (!holdingIndicators && privacyList.isNotEmpty()) { + // We are not holding, we were showing some indicators but now we should show nothing. + // Start holding. + logger.startIndicatorsHold(TIME_TO_HOLD_INDICATORS) + setHoldTimer() + } + // Else. We are not holding, we were not showing anything and the new list is empty. Ignore. } private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt index c88676e713b3..f3b8d2e5fbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -47,6 +47,26 @@ class PrivacyLogger @Inject constructor( }) } + fun startIndicatorsHold(time: Long) { + log(LogLevel.DEBUG, { + int1 = time.toInt() / 1000 + }, { + "Starting privacy indicators hold for $int1 seconds" + }) + } + + fun cancelIndicatorsHold() { + log(LogLevel.VERBOSE, {}, { + "Cancel privacy indicators hold" + }) + } + + fun finishIndicatorsHold() { + log(LogLevel.DEBUG, {}, { + "Finish privacy indicators hold" + }) + } + fun logCurrentProfilesChanged(profiles: List<Int>) { log(LogLevel.INFO, { str1 = profiles.toString() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 16e95900052f..e8976d379fef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -67,6 +67,7 @@ public class QSContainerImpl extends FrameLayout { private int mSideMargins; private boolean mQsDisabled; + private boolean mBackgroundVisible; private int mContentPadding = -1; private boolean mAnimateBottomOnNextLayout; @@ -122,6 +123,14 @@ public class QSContainerImpl extends FrameLayout { return true; } + /** + * If QS should have a solid or transparent background. + */ + public void setBackgroundVisible(boolean visible) { + mBackgroundVisible = visible; + updateBackgroundVisibility(); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the @@ -174,7 +183,11 @@ public class QSContainerImpl extends FrameLayout { final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; if (disabled == mQsDisabled) return; mQsDisabled = disabled; - mBackground.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); + updateBackgroundVisibility(); + } + + private void updateBackgroundVisibility() { + mBackground.setVisibility(mQsDisabled || !mBackgroundVisible ? GONE : VISIBLE); } void updateResources(QSPanelController qsPanelController, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index dbdd04a1e3ba..ed308ae1ac6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; @@ -105,6 +106,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QSPanelController mQSPanelController; private QuickQSPanelController mQuickQSPanelController; private QSCustomizerController mQSCustomizerController; + private FeatureFlags mFeatureFlags; @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -112,7 +114,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca StatusBarStateController statusBarStateController, CommandQueue commandQueue, QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, - QSFragmentComponent.Factory qsComponentFactory) { + QSFragmentComponent.Factory qsComponentFactory, FeatureFlags featureFlags) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mInjectionInflater = injectionInflater; mCommandQueue = commandQueue; @@ -122,6 +124,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQsComponentFactory = qsComponentFactory; commandQueue.observe(getLifecycle(), this); mHost = qsTileHost; + mFeatureFlags = featureFlags; mStatusBarStateController = statusBarStateController; } @@ -163,6 +166,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSContainerImplController = qsFragmentComponent.getQSContainerImplController(); mQSContainerImplController.init(); mContainer = mQSContainerImplController.getView(); + mContainer.setBackgroundVisible(!mFeatureFlags.isShadeOpaque()); mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter); mQSAnimator = qsFragmentComponent.getQSAnimator(); 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 98740a20a7c0..b6e07b1f8530 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -20,7 +20,6 @@ import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; -import android.annotation.StringRes; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -65,7 +64,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Override public @DrawableRes int getIconRes() { - return R.drawable.ic_camera_blocked; + return com.android.internal.R.drawable.ic_camera_blocked; } @Override 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 8cc0d7b4e8b8..9cc6f09f543c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -63,7 +63,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Override public @DrawableRes int getIconRes() { - return R.drawable.ic_mic_blocked; + return com.android.internal.R.drawable.ic_mic_blocked; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index f77431a7047e..8fc2830aa422 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -35,6 +35,8 @@ import android.util.Log; import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.exifinterface.media.ExifInterface; +import com.android.internal.annotations.VisibleForTesting; + import com.google.common.util.concurrent.ListenableFuture; import java.io.File; @@ -45,6 +47,7 @@ import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.UUID; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -72,7 +75,7 @@ class ImageExporter { private static final String EXIF_WRITE_EXCEPTION = "ExifInterface threw an exception writing to the file descriptor."; private static final String RESOLVER_UPDATE_ZERO_ROWS = - "Failed to publishEntry. ContentResolver#update reported no rows updated."; + "Failed to publish entry. ContentResolver#update reported no rows updated."; private static final String IMAGE_COMPRESS_RETURNED_FALSE = "Bitmap.compress returned false. (Failure unknown)"; @@ -114,8 +117,8 @@ class ImageExporter { * * @return a listenable future result */ - ListenableFuture<Uri> export(Executor executor, Bitmap bitmap) { - return export(executor, bitmap, ZonedDateTime.now()); + ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) { + return export(executor, requestId, bitmap, ZonedDateTime.now()); } /** @@ -126,8 +129,10 @@ class ImageExporter { * * @return a listenable future result */ - ListenableFuture<Uri> export(Executor executor, Bitmap bitmap, ZonedDateTime captureTime) { - final Task task = new Task(mResolver, bitmap, captureTime, mCompressFormat, mQuality); + ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, + ZonedDateTime captureTime) { + final Task task = + new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality); return CallbackToFutureAdapter.getFuture( (completer) -> { executor.execute(() -> { @@ -142,42 +147,62 @@ class ImageExporter { ); } + static class Result { + UUID requestId; + String fileName; + long timestamp; + Uri uri; + CompressFormat format; + } + private static class Task { private final ContentResolver mResolver; + private final UUID mRequestId; + private final Bitmap mBitmap; private final ZonedDateTime mCaptureTime; private final CompressFormat mFormat; private final int mQuality; - private final Bitmap mBitmap; + private final String mFileName; - Task(ContentResolver resolver, Bitmap bitmap, ZonedDateTime captureTime, + Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, CompressFormat format, int quality) { mResolver = resolver; + mRequestId = requestId; mBitmap = bitmap; mCaptureTime = captureTime; mFormat = format; mQuality = quality; + mFileName = createFilename(mCaptureTime, mFormat); } - public Uri execute() throws ImageExportException, InterruptedException { + public Result execute() throws ImageExportException, InterruptedException { Trace.beginSection("ImageExporter_execute"); Uri uri = null; Instant start = null; + Result result = new Result(); try { if (LogConfig.DEBUG_STORAGE) { Log.d(TAG, "image export started"); start = Instant.now(); } - uri = createEntry(mFormat, mCaptureTime); + + uri = createEntry(mFormat, mCaptureTime, mFileName); throwIfInterrupted(); writeImage(mBitmap, mFormat, mQuality, uri); throwIfInterrupted(); - writeExif(uri, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime); + writeExif(uri, mRequestId, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime); throwIfInterrupted(); publishEntry(uri); + result.timestamp = mCaptureTime.toInstant().toEpochMilli(); + result.requestId = mRequestId; + result.uri = uri; + result.fileName = mFileName; + result.format = mFormat; + if (LogConfig.DEBUG_STORAGE) { Log.d(TAG, "image export completed: " + Duration.between(start, Instant.now()).toMillis() + " ms"); @@ -190,13 +215,15 @@ class ImageExporter { } finally { Trace.endSection(); } - return uri; + return result; } - Uri createEntry(CompressFormat format, ZonedDateTime time) throws ImageExportException { + Uri createEntry(CompressFormat format, ZonedDateTime time, String fileName) + throws ImageExportException { Trace.beginSection("ImageExporter_createEntry"); try { - final ContentValues values = createMetadata(time, format); + final ContentValues values = createMetadata(time, format, fileName); + Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri == null) { throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); @@ -225,7 +252,7 @@ class ImageExporter { } } - void writeExif(Uri uri, int width, int height, ZonedDateTime captureTime) + void writeExif(Uri uri, UUID requestId, int width, int height, ZonedDateTime captureTime) throws ImageExportException { Trace.beginSection("ImageExporter_writeExif"); ParcelFileDescriptor pfd = null; @@ -241,7 +268,7 @@ class ImageExporter { throw new ImageExportException(EXIF_READ_EXCEPTION, e); } - updateExifAttributes(exif, width, height, captureTime); + updateExifAttributes(exif, requestId, width, height, captureTime); try { exif.saveAttributes(); } catch (IOException e) { @@ -276,14 +303,16 @@ class ImageExporter { } } + @VisibleForTesting static String createFilename(ZonedDateTime time, CompressFormat format) { return String.format(FILENAME_PATTERN, time, fileExtension(format)); } - static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format) { + static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format, + String fileName) { ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.RELATIVE_PATH, SCREENSHOTS_PATH); - values.put(MediaStore.MediaColumns.DISPLAY_NAME, createFilename(captureTime, format)); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); values.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(format)); values.put(MediaStore.MediaColumns.DATE_ADDED, captureTime.toEpochSecond()); values.put(MediaStore.MediaColumns.DATE_MODIFIED, captureTime.toEpochSecond()); @@ -293,8 +322,10 @@ class ImageExporter { return values; } - static void updateExifAttributes(ExifInterface exif, int width, int height, + static void updateExifAttributes(ExifInterface exif, UUID uniqueId, int width, int height, ZonedDateTime captureTime) { + exif.setAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID, uniqueId.toString()); + exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY); exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width)); exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height)); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 9dce19192dbe..0106f4316390 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -29,6 +29,7 @@ import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; @@ -49,8 +50,9 @@ import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; +import com.google.common.util.concurrent.ListenableFuture; + import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -65,7 +67,6 @@ import java.util.function.Supplier; class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String TAG = logTag(SaveImageInBackgroundTask.class); - private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; @@ -73,14 +74,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotSmartActions mScreenshotSmartActions; private final ScreenshotController.SaveImageInBackgroundData mParams; private final ScreenshotController.SavedImageData mImageData; - private final String mImageFileName; - private final long mImageTime; + private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; - private final String mScreenshotId; + private String mScreenshotId; private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); private final Supplier<ActionTransition> mSharedElementTransition; private final ImageExporter mImageExporter; + private long mImageTime; SaveImageInBackgroundTask(Context context, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, @@ -94,10 +95,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Prepare all the output metadata mParams = data; - mImageTime = System.currentTimeMillis(); - String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime)); - mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); - mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -121,18 +118,26 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } return null; } + // TODO: move to constructor / from ScreenshotRequest + final UUID requestId = UUID.randomUUID(); + final UserHandle user = getUserHandleOfForegroundApplication(mContext); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Bitmap image = mParams.image; - + mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId); try { // Call synchronously here since already on a background thread. - Uri uri = mImageExporter.export(Runnable::run, image).get(); + ListenableFuture<ImageExporter.Result> future = + mImageExporter.export(Runnable::run, requestId, image); + ImageExporter.Result result = future.get(); + final Uri uri = result.uri; + mImageTime = result.timestamp; CompletableFuture<List<Notification.Action>> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( mScreenshotId, uri, image, mSmartActionsProvider, - mSmartActionsEnabled, getUserHandle(mContext)); + mSmartActionsEnabled, user); List<Notification.Action> smartActions = new ArrayList<>(); if (mSmartActionsEnabled) { @@ -336,22 +341,21 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { return deleteActionBuilder.build(); } - private int getUserHandleOfForegroundApplication(Context context) { + private UserHandle getUserHandleOfForegroundApplication(Context context) { + UserManager manager = UserManager.get(context); + int result; // This logic matches // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile try { - return ActivityTaskManager.getService().getLastResumedActivityUserId(); + result = ActivityTaskManager.getService().getLastResumedActivityUserId(); } catch (RemoteException e) { if (DEBUG_ACTIONS) { Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); } - return context.getUserId(); + result = context.getUserId(); } - } - - private UserHandle getUserHandle(Context context) { - UserManager manager = UserManager.get(context); - return manager.getUserInfo(getUserHandleOfForegroundApplication(context)).getUserHandle(); + UserInfo userInfo = manager.getUserInfo(result); + return userInfo.getUserHandle(); } private List<Notification.Action> buildSmartActions( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index d77d1ea75f30..8801b253d054 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -55,6 +55,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.DisplayMetrics; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index c75efbcc5f80..54b1b2c8c54c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -29,6 +29,8 @@ import com.android.systemui.screenshot.ScrollCaptureClient.Session; import com.google.common.util.concurrent.ListenableFuture; +import java.time.ZonedDateTime; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -52,6 +54,9 @@ public class ScrollCaptureController { private final ImageExporter mImageExporter; private final ImageTileSet mImageTileSet; + private ZonedDateTime mCaptureTime; + private UUID mRequestId; + public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor, Executor bgExecutor, ImageExporter exporter) { mContext = context; @@ -68,6 +73,8 @@ public class ScrollCaptureController { * @param after action to take after the flow is complete */ public void run(final Runnable after) { + mCaptureTime = ZonedDateTime.now(); + mRequestId = UUID.randomUUID(); mConnection.start((session) -> startCapture(session, after)); } @@ -109,11 +116,12 @@ public class ScrollCaptureController { void exportToFile(Bitmap bitmap, Session session, Runnable afterEnd) { mImageExporter.setFormat(Bitmap.CompressFormat.PNG); mImageExporter.setQuality(6); - ListenableFuture<Uri> future = - mImageExporter.export(mBgExecutor, bitmap); + ListenableFuture<ImageExporter.Result> future = + mImageExporter.export(mBgExecutor, mRequestId, bitmap, mCaptureTime); future.addListener(() -> { try { - launchViewer(future.get()); + ImageExporter.Result result = future.get(); + launchViewer(result.uri); } catch (InterruptedException | ExecutionException e) { Toast.makeText(mContext, "Failed to write image", Toast.LENGTH_SHORT).show(); Log.e(TAG, "Error storing screenshot to media store", e.getCause()); diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt new file mode 100644 index 000000000000..c1161f1a0863 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.sensorprivacy + +import android.app.AppOpsManager +import android.content.DialogInterface +import android.content.Intent.EXTRA_PACKAGE_NAME +import android.content.pm.PackageManager +import android.content.res.Resources +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.* +import android.os.Bundle +import android.text.Html +import com.android.internal.app.AlertActivity +import com.android.systemui.R + +/** + * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is + * currently in "sensor privacy mode", aka. muted. + * + * <p>The dialog is started for the user the app is running for which might be a secondary users. + */ +class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListener { + private var sensor = -1 + private lateinit var sensorUsePackageName: String + + private lateinit var sensorPrivacyManager: SensorPrivacyManager + private lateinit var appOpsManager: AppOpsManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setResult(RESULT_CANCELED) + sensorPrivacyManager = getSystemService(SensorPrivacyManager::class.java)!! + appOpsManager = getSystemService(AppOpsManager::class.java)!! + + sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return + sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also { + if (it == -1) { + finish() + return + } + } + + sensorPrivacyManager.addSensorPrivacyListener(sensor) { isBlocked -> + if (!isBlocked) { + dismiss() + } + } + if (!sensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)) { + finish() + return + } + + mAlertParams.apply { + try { + mMessage = Html.fromHtml(getString(when (sensor) { + INDIVIDUAL_SENSOR_MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_content + INDIVIDUAL_SENSOR_CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_content + else -> Resources.ID_NULL + }, packageManager.getApplicationInfo(sensorUsePackageName, 0) + .loadLabel(packageManager)), 0) + } catch (e: PackageManager.NameNotFoundException) { + finish() + return + } + + mIconId = when (sensor) { + INDIVIDUAL_SENSOR_MICROPHONE -> + com.android.internal.R.drawable.perm_group_microphone + INDIVIDUAL_SENSOR_CAMERA -> com.android.internal.R.drawable.perm_group_camera + else -> Resources.ID_NULL + } + + mPositiveButtonText = getString( + com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button) + mNegativeButtonText = getString(android.R.string.cancel) + mPositiveButtonListener = this@SensorUseStartedActivity + mNegativeButtonListener = this@SensorUseStartedActivity + } + + setupAlert() + } + + override fun onStart() { + super.onStart() + + sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true) + } + + override fun onClick(dialog: DialogInterface?, which: Int) { + when (which) { + BUTTON_POSITIVE -> { + sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false) + setResult(RESULT_OK) + } + } + + dismiss() + } + + override fun onStop() { + super.onDestroy() + + sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 964c499e4b42..d6db7360fb21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -44,6 +44,10 @@ public class FeatureFlags { return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering); } + public boolean isShadeOpaque() { + return mFlagReader.isEnabled(R.bool.flag_shade_is_opaque); + } + /** b/171917882 */ public boolean isTwoColumnNotificationShadeEnabled() { return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 5259aa968825..c816784a22f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -855,5 +855,13 @@ public class KeyguardIndicationController implements StateListener, updateIndication(false); } } + + @Override + public void onRequireUnlockForNfc() { + showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc), + false /* isError */, false /* hideOnScreenOff */); + hideTransientIndicationDelayed(HIDE_DELAY_MS); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 86377fb014ae..2f0f90d318eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -86,6 +86,35 @@ object LiftReveal : LightRevealEffect { } } +class PowerButtonReveal( + /** Approximate Y-value of the center of the power button on the physical device. */ + val powerButtonY: Float +) : LightRevealEffect { + + private val OVAL_INITIAL_HEIGHT = 50f + + private val WIDTH_INCREASE_MULTIPLIER = 1.25f + + override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { + val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount) + val fadeAmount = + LightRevealEffect.getPercentPastThreshold(interpolatedAmount, 0.5f) + + with(scrim) { + revealGradientEndColorAlpha = 1f - fadeAmount + setRevealGradientBounds( + width - + width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount, + powerButtonY - (OVAL_INITIAL_HEIGHT / 2f) - + height * interpolatedAmount, + width * WIDTH_INCREASE_MULTIPLIER + + width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount, + powerButtonY + (OVAL_INITIAL_HEIGHT / 2f) + + height * interpolatedAmount) + } + } +} + /** * Scrim view that partially reveals the content underneath it using a [RadialGradient] with a * transparent center. The center position, size, and stops of the gradient can be manipulated to diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 20efa32d63c6..4a8057201c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -205,6 +205,30 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** + * @return whether to clip bottom of given view + */ + private boolean shouldClipBottom(ExpandableView view) { + final boolean showShelf = ((ShelfState) getViewState()).hasItemsInStableShelf; + if (showShelf) { + if (mAmbientState.isShadeOpening()) { + final float viewEnd = view.getTranslationY() + + view.getActualHeight() + + mPaddingBetweenElements; + final float finalShelfStart = mMaxLayoutHeight - getIntrinsicHeight(); + // While the shade is opening, only clip view if it overlaps with shelf; + // otherwise leave view unclipped. + if (viewEnd < finalShelfStart) { + return false; + } + } + // Clip for scrolling. + return true; + } + // Don't clip since we have enough space to show all views. + return false; + } + + /** * Update the shelf appearance based on the other notifications around it. This transforms * the icons from the notification area into the shelf. */ @@ -345,7 +369,9 @@ public class NotificationShelf extends ActivatableNotificationView implements clipTransientViews(); setClipTopAmount(clipTopAmount); - boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight(); + boolean isHidden = getViewState().hidden + || clipTopAmount >= getIntrinsicHeight() + || mAmbientState.isShadeOpening(); if (mShowNotificationShelf) { setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); } @@ -468,7 +494,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } else { shouldClipOwnTop = view.showingPulsing(); } - if (viewEnd > notificationClipEnd && !shouldClipOwnTop + if (shouldClipBottom(view) + && viewEnd > notificationClipEnd && !shouldClipOwnTop && (mAmbientState.isShadeExpanded() || !isPinned)) { int clipBottomAmount = (int) (viewEnd - notificationClipEnd); if (isPinned) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 1326d920fe42..e391250dc8fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -96,6 +96,8 @@ class NotificationWakeUpCoordinator @Inject constructor( } } + private var animatingScreenOff = false + private var collapsedEnoughToHide: Boolean = false var pulsing: Boolean = false @@ -232,9 +234,14 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onDozeAmountChanged(linear: Float, eased: Float) { - if (updateDozeAmountIfBypass()) { + if (overrideDozeAmountIfBypass()) { + return + } + + if (overrideDozeAmountIfAnimatingScreenOff(linear)) { return } + if (linear != 1.0f && linear != 0.0f && (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) { // Let's notify the scroller that an animation started @@ -257,7 +264,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onStateChanged(newState: Int) { - updateDozeAmountIfBypass() + overrideDozeAmountIfBypass() if (bypassController.bypassEnabled && newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED && (!statusBarStateController.isDozing || shouldAnimateVisibility())) { @@ -265,6 +272,14 @@ class NotificationWakeUpCoordinator @Inject constructor( setNotificationsVisible(visible = true, increaseSpeed = false, animate = false) setNotificationsVisible(visible = false, increaseSpeed = false, animate = true) } + + // If we want to control the screen off animation, check whether we are going from SHADE to + // KEYGUARD. + if (dozeParameters.shouldControlUnlockedScreenOff()) { + animatingScreenOff = + state == StatusBarState.SHADE && newState == StatusBarState.KEYGUARD + } + this.state = newState } @@ -280,7 +295,11 @@ class NotificationWakeUpCoordinator @Inject constructor( } } - private fun updateDozeAmountIfBypass(): Boolean { + /** + * @return Whether the doze amount was overridden because bypass is enabled. If true, the + * original doze amount should be ignored. + */ + private fun overrideDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { var amount = 1.0f if (statusBarStateController.state == StatusBarState.SHADE || @@ -293,6 +312,28 @@ class NotificationWakeUpCoordinator @Inject constructor( return false } + /** + * If we're playing the screen off animation, force the notification doze amount to be 1f (fully + * dozing). This is needed so that the notifications aren't briefly visible as the screen turns + * off and dozeAmount goes from 1f to 0f. + * + * @return Whether the doze amount was overridden because we are playing the screen off + * animation. If true, the original doze amount should be ignored. + */ + private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean { + if (animatingScreenOff) { + if (linearDozeAmount == 1f) { + animatingScreenOff = false + return false + } + + setDozeAmount(1f, 1f) + return true + } + + return false + } + private fun startVisibilityAnimation(increaseSpeed: Boolean) { if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) { mVisibilityInterpolator = if (mNotificationsVisible) @@ -345,6 +386,8 @@ class NotificationWakeUpCoordinator @Inject constructor( override fun onDozingChanged(isDozing: Boolean) { if (isDozing) { setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) + } else { + animatingScreenOff = false } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 789e78e33671..bafa4a254d79 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -938,6 +938,7 @@ public final class NotificationEntry extends ListEntry { /** Whether or not the icon for this notification is visible in the shelf. */ public void setShelfIconVisible(boolean shelfIconVisible) { + if (row == null) return; mShelfIconVisible = shelfIconVisible; updateShelfIconVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index c7ac40346ce1..998ae9e55313 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -30,12 +30,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.concurrency.DelayableExecutor; -import java.util.HashMap; -import java.util.Map; - import javax.inject.Inject; /** @@ -75,9 +71,6 @@ public class AppOpsCoordinator implements Coordinator { public void attach(NotifPipeline pipeline) { mNotifPipeline = pipeline; - // extend the lifetime of foreground notification services to show for at least 5 seconds - mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender); - // filter out foreground service notifications that aren't necessary anymore mNotifPipeline.addPreGroupFilter(mNotifFilter); @@ -119,64 +112,6 @@ public class AppOpsCoordinator implements Coordinator { }; /** - * Extends the lifetime of foreground notification services such that they show for at least - * five seconds - */ - private final NotifLifetimeExtender mForegroundLifetimeExtender = - new NotifLifetimeExtender() { - private static final int MIN_FGS_TIME_MS = 5000; - private OnEndLifetimeExtensionCallback mEndCallback; - private Map<NotificationEntry, Runnable> mEndRunnables = new HashMap<>(); - - @Override - public String getName() { - return TAG; - } - - @Override - public void setCallback(OnEndLifetimeExtensionCallback callback) { - mEndCallback = callback; - } - - @Override - public boolean shouldExtendLifetime(NotificationEntry entry, int reason) { - if ((entry.getSbn().getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) == 0) { - return false; - } - - final long currTime = System.currentTimeMillis(); - final boolean extendLife = currTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS; - - if (extendLife) { - if (!mEndRunnables.containsKey(entry)) { - final Runnable endExtensionRunnable = () -> { - mEndRunnables.remove(entry); - mEndCallback.onEndLifetimeExtension( - mForegroundLifetimeExtender, - entry); - }; - - final Runnable cancelRunnable = mMainExecutor.executeDelayed( - endExtensionRunnable, - MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime())); - mEndRunnables.put(entry, cancelRunnable); - } - } - - return extendLife; - } - - @Override - public void cancelLifetimeExtension(NotificationEntry entry) { - Runnable cancelRunnable = mEndRunnables.remove(entry); - if (cancelRunnable != null) { - cancelRunnable.run(); - } - } - }; - - /** * Puts foreground service notifications into its own section. */ private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index ba03d01b20b0..92b381e58698 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -614,6 +614,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } } + public void setShouldFadeForShadeOpen(boolean shouldFade) { + if (!mViewState.gone) { + mViewState.setShouldFadeForShadeOpen(shouldFade); + } + } + /** * @return whether the current view doesn't add height to the overall content. This means that * if it is added to a list of items, its content will still have the same height. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java index 628c4e258e50..21b6863adf0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java @@ -88,6 +88,12 @@ public class ExpandableViewState extends ViewState { public boolean hideSensitive; public boolean belowSpeedBump; public boolean inShelf; + public boolean shouldFadeForShadeOpen; + + @Override + boolean shouldAnimateAlpha() { + return shouldFadeForShadeOpen; + } /** * A state indicating whether a headsup is currently fully visible, even when not scrolled. @@ -171,6 +177,10 @@ public class ExpandableViewState extends ViewState { } } + public void setShouldFadeForShadeOpen(boolean shouldFade) { + shouldFadeForShadeOpen = shouldFade; + } + @Override public void animateTo(View child, AnimationProperties properties) { super.animateTo(child, properties); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index f07d8740c3e9..a2ce9e1ad6cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3947,6 +3947,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) getChildAt(i); + child.setShouldFadeForShadeOpen(mAmbientState.isShadeOpening()); child.applyViewState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index e6efba7ca28b..4bf7be3ba330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -568,11 +568,14 @@ public class StackScrollAlgorithm { // Add padding before sections for overscroll effect. childViewState.yTranslation += ambientState.getSectionPadding(); } - if (childViewState.yTranslation >= shelfStart) { - childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild(); - childViewState.inShelf = true; - childViewState.headsUpIsVisible = false; - } + boolean show = childViewState.yTranslation < shelfStart + && !ambientState.isAppearing(); + childViewState.hidden = !show + && !child.isExpandAnimationRunning() + && !child.hasExpandingChild(); + childViewState.inShelf = !show; + childViewState.headsUpIsVisible = show; + childViewState.alpha = show ? 1f : 0f; } protected int getMaxAllowedChildHeight(View child) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 66a48f16b624..43f1f431e138 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -45,6 +45,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CORNER_RADIUS = 200; public static final int ANIMATION_DURATION_WAKEUP = 500; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; + public static final int ANIMATION_DURATION_FADE_IN = 700; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_SWIPE = 260; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java index 3da4e321c54d..abe5c698034e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FADE_IN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -56,6 +58,16 @@ public class ViewState implements Dumpable { return mAnimationFilter; } }; + + protected static final AnimationProperties ANIMATE_ALPHA = new AnimationProperties() { + AnimationFilter mAnimationFilter = new AnimationFilter(); + @Override + public AnimationFilter getAnimationFilter() { + mAnimationFilter.animateAlpha(); + return mAnimationFilter; + } + }.setDuration(ANIMATION_DURATION_FADE_IN); + private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; @@ -148,6 +160,10 @@ public class ViewState implements Dumpable { scaleY = view.getScaleY(); } + boolean shouldAnimateAlpha() { + return false; + } + /** * Applies a {@link ViewState} to a normal view. */ @@ -200,24 +216,26 @@ public class ViewState implements Dumpable { int oldVisibility = view.getVisibility(); boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE)); - boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA); - if (animatingAlpha) { - updateAlphaAnimation(view); + if (isAnimating(view, TAG_ANIMATOR_ALPHA)) { + startAlphaAnimation(view, NO_NEW_ANIMATIONS); } else if (view.getAlpha() != this.alpha) { - // apply layer type - boolean becomesFullyVisible = this.alpha == 1.0f; - boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible - && view.hasOverlappingRendering(); - int layerType = view.getLayerType(); - int newLayerType = newLayerTypeIsHardware - ? View.LAYER_TYPE_HARDWARE - : View.LAYER_TYPE_NONE; - if (layerType != newLayerType) { - view.setLayerType(newLayerType, null); + if (shouldAnimateAlpha()) { + startAlphaAnimation(view, ANIMATE_ALPHA); + } else { + // apply layer type + boolean becomesFullyVisible = this.alpha == 1.0f; + boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible + && view.hasOverlappingRendering(); + int layerType = view.getLayerType(); + int newLayerType = newLayerTypeIsHardware + ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_NONE; + if (layerType != newLayerType) { + view.setLayerType(newLayerType, null); + } + // apply alpha + view.setAlpha(this.alpha); } - - // apply alpha - view.setAlpha(this.alpha); } // apply visibility @@ -322,10 +340,6 @@ public class ViewState implements Dumpable { } } - private void updateAlphaAnimation(View view) { - startAlphaAnimation(view, NO_NEW_ANIMATIONS); - } - private void startAlphaAnimation(final View child, AnimationProperties properties) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); Float previousEndValue = getChildTag(child,TAG_END_ALPHA); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 64951448a543..8c2fa3349e4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -194,6 +194,16 @@ public class DozeParameters implements TunerService.Tunable, mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation); } + /** + * Whether we want to control the screen off animation when the device is unlocked. If we do, + * we'll animate in AOD before turning off the screen, rather than simply fading to black and + * then abruptly showing AOD. + */ + public boolean shouldControlUnlockedScreenOff() { + return getAlwaysOn() && SystemProperties.getBoolean( + "persist.sysui.show_new_screen_on_transitions", false); + } + private boolean getBoolean(String propName, int resId) { return SystemProperties.getBoolean(propName, mResources.getBoolean(resId)); } 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 1dfd1f3ef69c..57a64e440bf6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -196,13 +196,15 @@ public class KeyguardClockPositionAlgorithm { } public void run(Result result) { - final int y = getClockY(mPanelExpansion); + final int y = getClockY(mPanelExpansion, mDarkAmount); result.clockY = y; + result.clockYFullyDozing = getClockY( + 1.0f /* panelExpansion */, 1.0f /* darkAmount */); result.clockAlpha = getClockAlpha(y); result.stackScrollerPadding = mBypassEnabled ? mUnlockedStackScrollerPadding : y + mKeyguardStatusHeight; result.stackScrollerPaddingExpanded = mBypassEnabled ? mUnlockedStackScrollerPadding - : getClockY(1.0f) + mKeyguardStatusHeight; + : getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight; result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); result.clockScale = interpolate(getBurnInScale(), 1.0f, 1.0f - mDarkAmount); } @@ -259,7 +261,7 @@ public class KeyguardClockPositionAlgorithm { return (int) y; } - private int getClockY(float panelExpansion) { + private int getClockY(float panelExpansion, float darkAmount) { // Dark: Align the bottom edge of the clock at about half of the screen: float clockYDark = (mHasCustomClock ? getPreferredClockY() : getMaxClockY()) + burnInPreventionOffsetY(); @@ -273,7 +275,7 @@ public class KeyguardClockPositionAlgorithm { float clockY = MathUtils.lerp(clockYBouncer, clockYRegular, shadeExpansion); clockYDark = MathUtils.lerp(clockYBouncer, clockYDark, shadeExpansion); - float darkAmount = mBypassEnabled && !mHasCustomClock ? 1.0f : mDarkAmount; + darkAmount = mBypassEnabled && !mHasCustomClock ? 1.0f : darkAmount; if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { // This will keep the clock at the top but out of the cutout area @@ -295,7 +297,7 @@ public class KeyguardClockPositionAlgorithm { * @return Alpha from 0 to 1. */ private float getClockAlpha(int y) { - float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f))); + float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount))); alphaKeyguard *= (1f - mQsExpansion); alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard); return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); @@ -328,6 +330,11 @@ public class KeyguardClockPositionAlgorithm { public int clockY; /** + * The y translation of the clock when we're fully dozing. + */ + public int clockYFullyDozing; + + /** * The alpha value of the clock. */ public float clockAlpha; 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 3e0978435e28..2254ead99c84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3617,6 +3617,16 @@ public class NotificationPanelViewController extends PanelViewController { int oldState = mBarState; boolean keyguardShowing = statusBarState == KEYGUARD; + if (mDozeParameters.shouldControlUnlockedScreenOff() && isDozing() && keyguardShowing) { + // This means we're doing the screen off animation - position the keyguard status + // view where it'll be on AOD, so we can animate it in. + mKeyguardStatusViewController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.clockYFullyDozing, + mClockPositionResult.clockScale, + false); + } + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( statusBarState, keyguardFadingAway, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index fc1811b11d73..146314877cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -49,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.BlurUtils; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -125,6 +126,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump */ public static final float BUSY_SCRIM_ALPHA = 1f; + /** + * Scrim opacity that can have text on top. + */ + public static final float GAR_SCRIM_ALPHA = 0.6f; + static final int TAG_KEY_ANIM = R.id.scrim; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; @@ -199,10 +205,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, - BlurUtils blurUtils, ConfigurationController configurationController) { + BlurUtils blurUtils, ConfigurationController configurationController, + FeatureFlags featureFlags) { mScrimStateListener = lightBarController::setScrimState; - mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; + mDefaultScrimAlpha = featureFlags.isShadeOpaque() ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA; mBlurUtils = blurUtils; mKeyguardStateController = keyguardStateController; 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 8ea173bfdc58..e07c3a536f68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -193,6 +193,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; +import com.android.systemui.statusbar.PowerButtonReveal; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.StatusBarState; @@ -372,6 +373,7 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mWakeUpComingFromTouch; private PointF mWakeUpTouchLocation; private LightRevealScrim mLightRevealScrim; + private PowerButtonReveal mPowerButtonReveal; private final Object mQueueLock = new Object(); @@ -1382,7 +1384,7 @@ public class StatusBar extends SystemUI implements DemoMode, * @param why the reason for the wake up */ public void wakeUpIfDozing(long time, View where, String why) { - if (mDozing) { + if (mDozing && !mKeyguardViewMediator.isAnimatingScreenOff()) { mPowerManager.wakeUp( time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); mWakeUpComingFromTouch = true; @@ -2945,6 +2947,9 @@ public class StatusBar extends SystemUI implements DemoMode, if (mBrightnessMirrorController != null) { mBrightnessMirrorController.updateResources(); } + + mPowerButtonReveal = new PowerButtonReveal(mContext.getResources().getDimensionPixelSize( + R.dimen.global_actions_top_padding)); } // Visibility reporting @@ -3652,6 +3657,16 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); mKeyguardViewMediator.setDozing(mDozing); + final boolean usePowerButtonEffect = + (isDozing && mWakefulnessLifecycle.getLastSleepReason() + == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) + || (!isDozing && mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON); + + mLightRevealScrim.setRevealEffect(usePowerButtonEffect + ? mPowerButtonReveal + : LiftReveal.INSTANCE); + mNotificationsController.requestNotificationUpdate("onDozingChanged"); updateDozingState(); mDozeServiceHost.updateDozing(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 4cfbe69f175f..0074dbea1151 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -22,6 +22,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -50,6 +51,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { ConfigurationController mConfigurationController; @Mock NotificationIconAreaController mNotificationIconAreaController; + @Mock + DozeParameters mDozeParameters; private KeyguardStatusViewController mController; @@ -64,7 +67,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mKeyguardStateController, mKeyguardUpdateMonitor, mConfigurationController, - mNotificationIconAreaController); + mNotificationIconAreaController, + mDozeParameters); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index e967a5d607eb..6c3b37edbd15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -24,10 +24,7 @@ import static junit.framework.TestCase.fail; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; 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; @@ -54,8 +51,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.util.time.FakeSystemClock; -import junit.framework.Assert; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,8 +79,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( - mContext, mFsc, mEntryManager, mNotifPipeline, - mock(ForegroundServiceLifetimeExtender.class), mClock); + mContext, mFsc, mEntryManager, mNotifPipeline, mClock); ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = ArgumentCaptor.forClass(NotificationEntryListener.class); verify(mEntryManager).addNotificationEntryListener( diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java deleted file mode 100644 index 9a40421a353f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui; - -import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.Notification; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.statusbar.NotificationInteractionTracker; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ForegroundServiceNotificationListenerTest extends SysuiTestCase { - private ForegroundServiceLifetimeExtender mExtender; - private NotificationEntry mEntry; - private Notification mNotif; - private final FakeSystemClock mClock = new FakeSystemClock(); - - @Mock - private NotificationInteractionTracker mInteractionTracker; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mExtender = new ForegroundServiceLifetimeExtender(mInteractionTracker, mClock); - - mNotif = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text") - .build(); - - mEntry = new NotificationEntryBuilder() - .setCreationTime(mClock.uptimeMillis()) - .setNotification(mNotif) - .build(); - } - - /** - * ForegroundServiceLifetimeExtenderTest - */ - @Test - public void testShouldExtendLifetime_should_foreground() { - // Extend the lifetime of a FGS notification iff it has not been visible - // for the minimum time - mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; - - // No time has elapsed, keep showing - assertTrue(mExtender.shouldExtendLifetime(mEntry)); - } - - @Test - public void testShouldExtendLifetime_shouldNot_foreground() { - mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; - - // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1 - mClock.advanceTime(MIN_FGS_TIME_MS + 1); - assertFalse(mExtender.shouldExtendLifetime(mEntry)); - } - - @Test - public void testShouldExtendLifetime_shouldNot_notForeground() { - mNotif.flags = 0; - - // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1 - mClock.advanceTime(MIN_FGS_TIME_MS + 1); - assertFalse(mExtender.shouldExtendLifetime(mEntry)); - } - - @Test - public void testShouldExtendLifetime_shouldNot_interruped() { - // GIVEN a notification that would trigger lifetime extension - mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; - - // GIVEN the notification has alerted - mEntry.setInterruption(); - - // THEN the notification does not need to have its lifetime extended by this extender - assertFalse(mExtender.shouldExtendLifetime(mEntry)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 67d0295c82d3..00943bc53bfd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -44,6 +44,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; @@ -73,6 +75,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock TrustManager mTrustManager; private @Mock NavigationModeController mNavigationModeController; private @Mock KeyguardDisplayManager mKeyguardDisplayManager; + private @Mock DozeParameters mDozeParameters; + private @Mock StatusBarStateController mStatusBarStateController; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -91,7 +95,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mStatusBarKeyguardViewManager, mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor, mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController, - mKeyguardDisplayManager); + mKeyguardDisplayManager, mDozeParameters, mStatusBarStateController); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index 2e8e3ed664b3..42e88b0f393d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.os.PowerManager; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -58,7 +59,7 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { @Test public void dispatchStartedWakingUp() throws Exception { - mWakefulness.dispatchStartedWakingUp(); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); assertEquals(WakefulnessLifecycle.WAKEFULNESS_WAKING, mWakefulness.getWakefulness()); @@ -67,7 +68,7 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { @Test public void dispatchFinishedWakingUp() throws Exception { - mWakefulness.dispatchStartedWakingUp(); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulness.dispatchFinishedWakingUp(); assertEquals(WakefulnessLifecycle.WAKEFULNESS_AWAKE, mWakefulness.getWakefulness()); @@ -77,9 +78,9 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { @Test public void dispatchStartedGoingToSleep() throws Exception { - mWakefulness.dispatchStartedWakingUp(); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulness.dispatchFinishedWakingUp(); - mWakefulness.dispatchStartedGoingToSleep(); + mWakefulness.dispatchStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN); assertEquals(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP, mWakefulness.getWakefulness()); @@ -89,9 +90,9 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { @Test public void dispatchFinishedGoingToSleep() throws Exception { - mWakefulness.dispatchStartedWakingUp(); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulness.dispatchFinishedWakingUp(); - mWakefulness.dispatchStartedGoingToSleep(); + mWakefulness.dispatchStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN); mWakefulness.dispatchFinishedGoingToSleep(); assertEquals(WakefulnessLifecycle.WAKEFULNESS_ASLEEP, @@ -102,12 +103,12 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { @Test public void doesNotDispatchTwice() throws Exception { - mWakefulness.dispatchStartedWakingUp(); - mWakefulness.dispatchStartedWakingUp(); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); + mWakefulness.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulness.dispatchFinishedWakingUp(); mWakefulness.dispatchFinishedWakingUp(); - mWakefulness.dispatchStartedGoingToSleep(); - mWakefulness.dispatchStartedGoingToSleep(); + mWakefulness.dispatchStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN); + mWakefulness.dispatchStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN); mWakefulness.dispatchFinishedGoingToSleep(); mWakefulness.dispatchFinishedGoingToSleep(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt index c401fab1e1bc..132bee0e7fdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -107,7 +106,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testMicCameraChanged() { changeMicCamera(false) // default is true executor.runAllReady() @@ -140,7 +138,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testBothChanged() { changeAll(true) changeMicCamera(false) @@ -162,7 +159,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testMicCamera_listening() { changeMicCamera(true) executor.runAllReady() @@ -179,7 +175,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testAllFalse_notListening() { changeAll(true) executor.runAllReady() @@ -191,7 +186,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testSomeListening_stillListening() { // Mic and camera are true by default changeAll(true) @@ -203,7 +197,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testAllDeleted_micCameraFalse_stopListening() { changeMicCamera(false) changeAll(true) @@ -215,7 +208,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testMicDeleted_stillListening() { changeMicCamera(true) executor.runAllReady() diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index a8b305614a4a..7ca468edfd9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -97,6 +97,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var privacyItemController: PrivacyItemController private lateinit var executor: FakeExecutor + private lateinit var fakeClock: FakeSystemClock private lateinit var deviceConfigProxy: DeviceConfigProxy fun PrivacyItemController(): PrivacyItemController { @@ -113,7 +114,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - executor = FakeExecutor(FakeSystemClock()) + fakeClock = FakeSystemClock() + executor = FakeExecutor(fakeClock) deviceConfigProxy = DeviceConfigProxyFake() // Listen to everything by default @@ -420,6 +422,104 @@ class PrivacyItemControllerTest : SysuiTestCase() { assertEquals(PrivacyType.TYPE_MICROPHONE, argCaptor.value[0].privacyType) } + @Test + fun testPassageOfTimeDoesNotRemoveIndicators() { + doReturn(listOf( + AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) + )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + + fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS * 10) + executor.runAllReady() + + verify(callback, never()).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isNotEmpty()) + } + + @Test + fun testHoldingAfterEmptyBeforeTimeExpires() { + doReturn(listOf( + AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) + )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + executor.runAllReady() + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + + `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList()) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + executor.runAllReady() + + fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 5) + executor.runAllReady() + + verify(callback, never()).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isNotEmpty()) + } + + @Test + fun testAfterHoldingIndicatorsAreEmpty() { + doReturn(listOf( + AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) + )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + executor.runAllReady() + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + + `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList()) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + executor.runAllReady() + + executor.advanceClockToLast() + executor.runAllReady() + + verify(callback).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isEmpty()) + } + + @Test + fun testHoldingStopsIfNewIndicatorsAppear() { + doReturn(listOf( + AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) + )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + executor.runAllReady() + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + + `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList()) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + executor.runAllReady() + + fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 2) + executor.runAllReady() + + doReturn(listOf( + AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0) + )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true) + executor.runAllReady() + + executor.advanceClockToLast() + executor.runAllReady() + + verify(callback, never()).onPrivacyItemsChanged(emptyList()) + verify(callback, atLeastOnce()).onPrivacyItemsChanged(capture(argCaptor)) + + val lastList = argCaptor.allValues.last() + assertEquals(1, lastList.size) + assertEquals(PrivacyType.TYPE_MICROPHONE, lastList.single().privacyType) + } + private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index a6b0330743b3..1260eaf23c52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -87,6 +88,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { private MediaHost mQSMediaHost; @Mock private MediaHost mQQSMediaHost; + @Mock + private FeatureFlags mFeatureFlags; public QSFragmentTest() { super(QSFragment.class); @@ -175,6 +178,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { new QSDetailDisplayer(), mQSMediaHost, mQQSMediaHost, - mQsComponentFactory); + mQsComponentFactory, + mFeatureFlags); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java index f2bf7aa3d842..b0f78ad70303 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java @@ -32,7 +32,6 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; @@ -55,6 +54,7 @@ import java.io.InputStream; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -79,10 +79,13 @@ public class ImageExporterTest extends SysuiTestCase { public void testUpdateExifAttributes_timeZoneUTC() throws IOException { ExifInterface exifInterface = new ExifInterface(new ByteArrayInputStream(EXIF_FILE_TAG), ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY); - - ImageExporter.updateExifAttributes(exifInterface, 100, 100, + ImageExporter.updateExifAttributes(exifInterface, + UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"), 100, 100, ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 18, 15), ZoneId.of("UTC"))); + assertEquals("Exif " + ExifInterface.TAG_IMAGE_UNIQUE_ID, + "3c11da99-9284-4863-b1d5-6f3684976814", + exifInterface.getAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID)); assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00", exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL)); } @@ -93,24 +96,38 @@ public class ImageExporterTest extends SysuiTestCase { ContentResolver contentResolver = context.getContentResolver(); ImageExporter exporter = new ImageExporter(contentResolver); + UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"); Bitmap original = createCheckerBitmap(10, 10, 10); - ListenableFuture<Uri> direct = exporter.export(DIRECT_EXECUTOR, original, CAPTURE_TIME); + ListenableFuture<ImageExporter.Result> direct = + exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME); assertTrue("future should be done", direct.isDone()); assertFalse("future should not be canceled", direct.isCancelled()); - Uri result = direct.get(); + ImageExporter.Result result = direct.get(); + + assertEquals("Result should contain the same request id", requestId, result.requestId); + assertEquals("Filename should contain the correct filename", + "Screenshot_20201215-131500.png", result.fileName); + assertNotNull("CompressFormat should be set", result.format); + assertEquals("The default CompressFormat should be PNG", CompressFormat.PNG, result.format); + assertNotNull("Uri should not be null", result.uri); + assertEquals("Timestamp should match input", CAPTURE_TIME.toInstant().toEpochMilli(), + result.timestamp); - assertNotNull("Uri should not be null", result); Bitmap decoded = null; - try (InputStream in = contentResolver.openInputStream(result)) { + try (InputStream in = contentResolver.openInputStream(result.uri)) { decoded = BitmapFactory.decodeStream(in); assertNotNull("decoded image should not be null", decoded); assertTrue("original and decoded image should be identical", original.sameAs(decoded)); - try (ParcelFileDescriptor pfd = contentResolver.openFile(result, "r", null)) { + try (ParcelFileDescriptor pfd = contentResolver.openFile(result.uri, "r", null)) { assertNotNull(pfd); ExifInterface exifInterface = new ExifInterface(pfd.getFileDescriptor()); + assertEquals("Exif " + ExifInterface.TAG_IMAGE_UNIQUE_ID, + "3c11da99-9284-4863-b1d5-6f3684976814", + exifInterface.getAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID)); + assertEquals("Exif " + ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY, exifInterface.getAttribute(ExifInterface.TAG_SOFTWARE)); @@ -130,13 +147,14 @@ public class ImageExporterTest extends SysuiTestCase { if (decoded != null) { decoded.recycle(); } - contentResolver.delete(result, null); + contentResolver.delete(result.uri, null); } } @Test public void testMediaStoreMetadata() { - ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG); + String name = ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG); + ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG, name); assertEquals("Pictures/Screenshots", values.getAsString(MediaStore.MediaColumns.RELATIVE_PATH)); assertEquals("Screenshot_20201215-131500.png", diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java index 639e791cbf23..09546216183f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.os.Bundle; import android.os.UserHandle; -import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -46,7 +45,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -71,7 +69,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { private NotificationEntryBuilder mEntryBuilder; private AppOpsCoordinator mAppOpsCoordinator; private NotifFilter mForegroundFilter; - private NotifLifetimeExtender mForegroundNotifLifetimeExtender; private NotifSectioner mFgsSection; private FakeSystemClock mClock = new FakeSystemClock(); @@ -98,13 +95,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); mForegroundFilter = filterCaptor.getValue(); - // capture lifetime extender - ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor = - ArgumentCaptor.forClass(NotifLifetimeExtender.class); - verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender( - lifetimeExtenderCaptor.capture()); - mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue(); - mFgsSection = mAppOpsCoordinator.getSectioner(); } @@ -160,55 +150,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { } @Test - public void extendLifetimeText_notForeground() { - // GIVEN the notification doesn't represent a foreground service - mEntryBuilder.modifyNotification(mContext) - .setFlag(FLAG_FOREGROUND_SERVICE, false); - - // THEN don't extend the lifetime - assertFalse(mForegroundNotifLifetimeExtender - .shouldExtendLifetime(mEntryBuilder.build(), - NotificationListenerService.REASON_CLICK)); - } - - @Test - public void extendLifetimeText_foregroundNotifRecentlyPosted() { - // GIVEN the notification represents a foreground service that was just posted - Notification notification = new Notification.Builder(mContext, "test_channel") - .setFlag(FLAG_FOREGROUND_SERVICE, true) - .build(); - NotificationEntry entry = mEntryBuilder - .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "", - NOTIF_USER_ID, NOTIF_USER_ID, notification, - new UserHandle(NOTIF_USER_ID), "", System.currentTimeMillis())) - .setNotification(notification) - .build(); - - // THEN extend the lifetime - assertTrue(mForegroundNotifLifetimeExtender - .shouldExtendLifetime(entry, NotificationListenerService.REASON_CLICK)); - } - - @Test - public void extendLifetimeText_foregroundNotifOld() { - // GIVEN the notification represents a foreground service that was posted 10 seconds ago - Notification notification = new Notification.Builder(mContext, "test_channel") - .setFlag(FLAG_FOREGROUND_SERVICE, true) - .build(); - NotificationEntry entry = mEntryBuilder - .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "", - NOTIF_USER_ID, NOTIF_USER_ID, notification, - new UserHandle(NOTIF_USER_ID), "", - System.currentTimeMillis() - 10000)) - .setNotification(notification) - .build(); - - // THEN don't extend the lifetime because the extended time exceeds MIN_FGS_TIME_MS - assertFalse(mForegroundNotifLifetimeExtender - .shouldExtendLifetime(entry, NotificationListenerService.REASON_CLICK)); - } - - @Test public void testIncludeFGSInSection_importanceDefault() { // GIVEN the notification represents a colorized foreground service with > min importance mEntryBuilder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 342b2f57396e..30c3e6d85875 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -50,6 +50,7 @@ import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.BlurUtils; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -104,6 +105,8 @@ public class ScrimControllerTest extends SysuiTestCase { private BlurUtils mBlurUtils; @Mock private ConfigurationController mConfigurationController; + @Mock + private FeatureFlags mFeatureFlags; private static class AnimatorListener implements Animator.AnimatorListener { @@ -211,13 +214,13 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDelayedWakeLockBuilder.setTag(any(String.class))) .thenReturn(mDelayedWakeLockBuilder); when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); - + when(mFeatureFlags.isShadeOpaque()).thenReturn(true); when(mDockManager.isDocked()).thenReturn(false); mScrimController = new ScrimController(mLightBarController, mDozeParamenters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, - mDockManager, mBlurUtils, mConfigurationController); + mDockManager, mBlurUtils, mConfigurationController, mFeatureFlags); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mScrimInFront, mScrimForBubble); mScrimController.setAnimatorListener(mAnimatorListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 51ce8e59e999..2c781bad10c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -317,7 +317,7 @@ public class StatusBarTest extends SysuiTestCase { when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle(); - wakefulnessLifecycle.dispatchStartedWakingUp(); + wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); wakefulnessLifecycle.dispatchFinishedWakingUp(); when(mGradientColors.supportsDarkText()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 273a77b36c78..c83835b5453e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -62,6 +62,7 @@ import android.util.Log; import androidx.test.InstrumentationRegistry; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.settingslib.R; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.MobileMappings.Config; @@ -82,6 +83,8 @@ import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.io.PrintWriter; import java.io.StringWriter; @@ -126,6 +129,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { private ConnectivityManager.NetworkCallback mDefaultCallbackInNetworkController; private ConnectivityManager.NetworkCallback mNetworkCallback; + MockitoSession mMockingSession = null; + @Rule public TestWatcher failWatcher = new TestWatcher() { @Override @@ -145,7 +150,11 @@ public class NetworkControllerBaseTest extends SysuiTestCase { @Before public void setUp() throws Exception { - FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, true); + mMockingSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT) + .mockStatic(FeatureFlagUtils.class).startMocking(); + ExtendedMockito.doReturn(true).when(() -> FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); Settings.Global.putInt(mContext.getContentResolver(), Global.AIRPLANE_MODE_ON, 0); TestableResources res = mContext.getOrCreateTestableResources(); @@ -227,7 +236,9 @@ public class NetworkControllerBaseTest extends SysuiTestCase { @After public void tearDown() throws Exception { - FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, false); + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } } protected void setupNetworkController() { diff --git a/services/Android.bp b/services/Android.bp index da24719c0f68..b11a2e8bfa4f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -147,11 +147,20 @@ droidstubs { baseline_file: "api/lint-baseline.txt", }, }, - dist: { - targets: ["sdk", "win_sdk"], - dir: "apistubs/android/system-server/api", - dest: "android.txt", - }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "android.txt", + tag: ".api.txt" + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "removed.txt", + tag: ".removed-api.txt", + }, + ] } java_library { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 7483ff3ef3f4..2434e2ca0b54 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -141,12 +141,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH public FullScreenMagnificationGestureHandler(Context context, FullScreenMagnificationController fullScreenMagnificationController, - ScaleChangedListener listener, + Callback callback, boolean detectTripleTap, boolean detectShortcutTrigger, @NonNull WindowMagnificationPromptController promptController, int displayId) { - super(displayId, detectTripleTap, detectShortcutTrigger, listener); + super(displayId, detectTripleTap, detectShortcutTrigger, callback); if (DEBUG_ALL) { Log.i(mLogTag, "FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap @@ -211,16 +211,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } @Override - public void notifyShortcutTriggered() { - if (mDetectShortcutTrigger) { - boolean wasMagnifying = mFullScreenMagnificationController.resetIfNeeded(mDisplayId, - /* animate */ true); - if (wasMagnifying) { - clearAndTransitionToStateDetecting(); - } else { - mPromptController.showNotificationIfNeeded(); - mDetectingState.toggleShortcutTriggered(); - } + public void handleShortcutTriggered() { + boolean wasMagnifying = mFullScreenMagnificationController.resetIfNeeded(mDisplayId, + /* animate */ true); + if (wasMagnifying) { + clearAndTransitionToStateDetecting(); + } else { + mPromptController.showNotificationIfNeeded(); + mDetectingState.toggleShortcutTriggered(); } } @@ -395,7 +393,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x"); mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - mListener.onMagnificationScaleChanged(mDisplayId, getMode()); return /* handled: */ true; } @@ -869,6 +866,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mPromptController.showNotificationIfNeeded(); zoomOn(up.getX(), up.getY()); } + + mCallback.onTripleTapped(mDisplayId, getMode()); } private boolean isMagnifying() { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index df88ceb95d9e..2a65b6423158 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -34,11 +34,24 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; /** - * Handles all magnification controllers initialization, generic interactions - * and magnification mode transition. + * Handles all magnification controllers initialization, generic interactions, + * magnification mode transition and magnification switch UI show/hide logic + * in the following callbacks: + * + * <ol> + * <li> 1. {@link #onTouchInteractionStart} shows magnification switch UI when + * the user touch interaction starts if magnification capabilities is all. </li> + * <li> 2. {@link #onTouchInteractionEnd} shows magnification switch UI when + * the user touch interaction ends if magnification capabilities is all. </li> + * <li> 3. {@link #onShortcutTriggered} updates magnification switch UI depending on + * magnification capabilities and magnification active state when magnification shortcut + * is triggered.</li> + * <li> 4. {@link #onTripleTapped} updates magnification switch UI depending on magnification + * capabilities and magnification active state when triple-tap gesture is detected. </li> + * </ol> */ public class MagnificationController implements WindowMagnificationManager.Callback, - MagnificationGestureHandler.ScaleChangedListener { + MagnificationGestureHandler.Callback { private static final boolean DEBUG = false; private static final String TAG = "MagnificationController"; @@ -84,16 +97,44 @@ public class MagnificationController implements WindowMagnificationManager.Callb public void onPerformScaleAction(int displayId, float scale) { getWindowMagnificationMgr().setScale(displayId, scale); getWindowMagnificationMgr().persistScale(displayId); - onMagnificationScaleChanged(displayId, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } @Override - public void onMagnificationScaleChanged(int displayId, int mode) { + public void onTouchInteractionStart(int displayId, int mode) { + handleUserInteractionChanged(displayId, mode); + } + + @Override + public void onTouchInteractionEnd(int displayId, int mode) { + handleUserInteractionChanged(displayId, mode); + } + + private void handleUserInteractionChanged(int displayId, int mode) { if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { return; } - getWindowMagnificationMgr().showMagnificationButton(displayId, mode); + if (isActivated(displayId, mode)) { + getWindowMagnificationMgr().showMagnificationButton(displayId, mode); + } + } + + @Override + public void onShortcutTriggered(int displayId, int mode) { + updateMagnificationButton(displayId, mode); + } + + @Override + public void onTripleTapped(int displayId, int mode) { + updateMagnificationButton(displayId, mode); + } + + private void updateMagnificationButton(int displayId, int mode) { + if (isActivated(displayId, mode) && mMagnificationCapabilities + == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { + getWindowMagnificationMgr().showMagnificationButton(displayId, mode); + } else { + getWindowMagnificationMgr().removeMagnificationButton(displayId); + } } /** @@ -272,6 +313,18 @@ public class MagnificationController implements WindowMagnificationManager.Callb return mTempPoint; } + private boolean isActivated(int displayId, int mode) { + boolean isActivated = false; + if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + && mFullScreenMagnificationController != null) { + isActivated = mFullScreenMagnificationController.isMagnifying(displayId); + } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW + && mWindowMagnificationMgr != null) { + isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); + } + return isActivated; + } + private final class DisableMagnificationCallback implements MagnificationAnimationCallback { private final TransitionCallBack mTransitionCallBack; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 386d0bbf35ec..bbe40b6faf74 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -17,6 +17,8 @@ package com.android.server.accessibility.magnification; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_UP; import android.annotation.NonNull; import android.util.Log; @@ -59,26 +61,53 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo */ protected final boolean mDetectTripleTap; - /** Interface for listening to the magnification scaling gesture. */ - public interface ScaleChangedListener { + /** Callback interface to report that magnification is interactive with a user. */ + public interface Callback { /** - * Called when the magnification scale is changed by users. + * Called when the touch interaction is started by a user. * * @param displayId The logical display id - * @param mode The magnification mode + * @param mode The magnification mode */ - void onMagnificationScaleChanged(int displayId, int mode); + void onTouchInteractionStart(int displayId, int mode); + + /** + * Called when the touch interaction is ended by a user. + * + * @param displayId The logical display id + * @param mode The magnification mode + */ + void onTouchInteractionEnd(int displayId, int mode); + + /** + * Called when the magnification shortcut is triggered by a user. The magnification + * shortcut can be accessibility button or volume shortcut. + * + * @param displayId The logical display id + * @param mode The magnification mode + */ + void onShortcutTriggered(int displayId, int mode); + + /** + * Called when the triple-tap gesture is handled. The magnification + * shortcut can be a triple-tap gesture or accessibility button. + * Called when the triple-tap gesture is handled + * + * @param displayId The logical display id + * @param mode The magnification mode + */ + void onTripleTapped(int displayId, int mode); } - protected final ScaleChangedListener mListener; + protected final Callback mCallback; protected MagnificationGestureHandler(int displayId, boolean detectTripleTap, boolean detectShortcutTrigger, - @NonNull ScaleChangedListener listener) { + @NonNull Callback callback) { mDisplayId = displayId; mDetectTripleTap = detectTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; - mListener = listener; + mCallback = callback; mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null; mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null; @@ -96,6 +125,13 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo dispatchTransformedEvent(event, rawEvent, policyFlags); } else { onMotionEventInternal(event, rawEvent, policyFlags); + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mCallback.onTouchInteractionStart(mDisplayId, getMode()); + } else if (action == ACTION_UP || action == ACTION_CANCEL) { + mCallback.onTouchInteractionEnd(mDisplayId, getMode()); + } } } @@ -107,8 +143,7 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo return false; } - final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { + final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (DEBUG_EVENT_STREAM) { storeEventInto(mDebugOutputEventHistory, event); try { @@ -140,7 +175,20 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo /** * Called when the shortcut target is magnification. */ - public abstract void notifyShortcutTriggered(); + public void notifyShortcutTriggered() { + if (DEBUG_ALL) { + Slog.i(mLogTag, "notifyShortcutTriggered():"); + } + if (mDetectShortcutTrigger) { + handleShortcutTriggered(); + mCallback.onShortcutTriggered(mDisplayId, getMode()); + } + } + + /** + * Handles shortcut triggered event. + */ + abstract void handleShortcutTriggered(); /** * Indicates the magnification mode. diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 7f26b2755900..55a911eea821 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -88,9 +88,9 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl public WindowMagnificationGestureHandler(Context context, WindowMagnificationManager windowMagnificationMgr, - ScaleChangedListener listener, + Callback callback, boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) { - super(displayId, detectTripleTap, detectShortcutTrigger, listener); + super(displayId, detectTripleTap, detectShortcutTrigger, callback); if (DEBUG_ALL) { Slog.i(mLogTag, "WindowMagnificationGestureHandler() , displayId = " + displayId + ")"); @@ -115,7 +115,6 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public void setScale(int displayId, float scale) { mWindowMagnificationMgr.setScale(displayId, scale); - mListener.onMagnificationScaleChanged(displayId, getMode()); } @Override @@ -153,13 +152,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl } @Override - public void notifyShortcutTriggered() { - if (DEBUG_ALL) { - Slog.i(mLogTag, "notifyShortcutTriggered():"); - } - if (!mDetectShortcutTrigger) { - return; - } + public void handleShortcutTriggered() { final Point screenSize = mTempPoint; getScreenSize(mTempPoint); toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f); @@ -206,6 +199,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.i(mLogTag, "onTripleTap()"); } toggleMagnification(up.getX(), up.getY()); + mCallback.onTripleTapped(mDisplayId, getMode()); } void resetToDetectState() { diff --git a/services/appwidget/java/com/android/server/appwidget/OWNERS b/services/appwidget/java/com/android/server/appwidget/OWNERS new file mode 100644 index 000000000000..d724cac4aa3e --- /dev/null +++ b/services/appwidget/java/com/android/server/appwidget/OWNERS @@ -0,0 +1 @@ +include /core/java/android/appwidget/OWNERS diff --git a/services/core/Android.bp b/services/core/Android.bp index 3750f148f45c..2f3ad196dfd6 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -120,6 +120,9 @@ java_library_static { "time_zone_distro", "time_zone_distro_installer", "android.hardware.authsecret-V1.0-java", + "android.hardware.boot-V1.0-java", + "android.hardware.boot-V1.1-java", + "android.hardware.boot-V1.2-java", "android.hardware.broadcastradio-V2.0-java", "android.hardware.health-V1.0-java", "android.hardware.health-V2.0-java", @@ -223,6 +226,8 @@ filegroup { "java/com/android/server/connectivity/NetworkRanker.java", "java/com/android/server/connectivity/PermissionMonitor.java", "java/com/android/server/connectivity/ProxyTracker.java", + "java/com/android/server/connectivity/QosCallbackAgentConnection.java", + "java/com/android/server/connectivity/QosCallbackTracker.java", "java/com/android/server/connectivity/TcpKeepaliveController.java", "java/com/android/server/connectivity/Vpn.java", "java/com/android/server/connectivity/VpnIkev2Utils.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7541833b1569..b6232a0661ff 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -94,6 +94,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; import android.net.IpMemoryStore; @@ -121,6 +122,10 @@ import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSocketFilter; +import android.net.QosSocketInfo; import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; @@ -204,6 +209,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; +import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; import com.android.server.net.LockdownVpnTracker; @@ -279,6 +285,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // Default to 30s linger time-out. Modifiable only for testing. private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final int DEFAULT_LINGER_DELAY_MS = 30_000; + + // The maximum number of network request allowed per uid before an exception is thrown. + private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; + @VisibleForTesting protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it. @@ -291,6 +301,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting protected final PermissionMonitor mPermissionMonitor; + private final PerUidCounter mNetworkRequestCounter; + private KeyStore mKeyStore; @VisibleForTesting @@ -614,6 +626,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final LocationPermissionChecker mLocationPermissionChecker; private KeepaliveTracker mKeepaliveTracker; + private QosCallbackTracker mQosCallbackTracker; private NetworkNotificationManager mNotifier; private LingerMonitor mLingerMonitor; @@ -858,6 +871,66 @@ public class ConnectivityService extends IConnectivityManager.Stub }; /** + * Keeps track of the number of requests made under different uids. + */ + public static class PerUidCounter { + private final int mMaxCountPerUid; + + // Map from UID to number of NetworkRequests that UID has filed. + @GuardedBy("mUidToNetworkRequestCount") + private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); + + /** + * Constructor + * + * @param maxCountPerUid the maximum count per uid allowed + */ + public PerUidCounter(final int maxCountPerUid) { + mMaxCountPerUid = maxCountPerUid; + } + + /** + * Increments the request count of the given uid. Throws an exception if the number + * of open requests for the uid exceeds the value of maxCounterPerUid which is the value + * passed into the constructor. see: {@link #PerUidCounter(int)}. + * + * @throws ServiceSpecificException with + * {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for + * the uid exceed the allowed number. + * + * @param uid the uid that the request was made under + */ + public void incrementCountOrThrow(final int uid) { + synchronized (mUidToNetworkRequestCount) { + final int networkRequests = mUidToNetworkRequestCount.get(uid, 0) + 1; + if (networkRequests >= mMaxCountPerUid) { + throw new ServiceSpecificException( + ConnectivityManager.Errors.TOO_MANY_REQUESTS); + } + mUidToNetworkRequestCount.put(uid, networkRequests); + } + } + + /** + * Decrements the request count of the given uid. + * + * @param uid the uid that the request was made under + */ + public void decrementCount(final int uid) { + synchronized (mUidToNetworkRequestCount) { + final int requests = mUidToNetworkRequestCount.get(uid, 0); + if (requests < 1) { + logwtf("BUG: too small request count " + requests + " for UID " + uid); + } else if (requests == 1) { + mUidToNetworkRequestCount.delete(uid); + } else { + mUidToNetworkRequestCount.put(uid, requests - 1); + } + } + } + } + + /** * Dependencies of ConnectivityService, for injection in tests. */ @VisibleForTesting @@ -945,6 +1018,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mSystemProperties = mDeps.getSystemProperties(); mNetIdManager = mDeps.makeNetIdManager(); mContext = Objects.requireNonNull(context, "missing Context"); + mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID); mMetricsLog = logger; mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); @@ -1115,11 +1189,7 @@ public class ConnectivityService extends IConnectivityManager.Stub userAllContext.registerReceiver( mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); - try { - mNMS.registerObserver(mDataActivityObserver); - } catch (RemoteException e) { - loge("Error registering observer :" + e); - } + mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); mSettingsObserver = new SettingsObserver(mContext, mHandler); registerSettingsCallbacks(); @@ -1129,6 +1199,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager); + mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter); final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, @@ -1802,30 +1873,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() { - @Override - public void interfaceClassDataActivityChanged(int transportType, boolean active, - long tsNanos, int uid) { - sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos); - } - }; - - // This is deprecated and only to support legacy use cases. - private int transportTypeToLegacyType(int type) { - switch (type) { - case NetworkCapabilities.TRANSPORT_CELLULAR: - return ConnectivityManager.TYPE_MOBILE; - case NetworkCapabilities.TRANSPORT_WIFI: - return ConnectivityManager.TYPE_WIFI; - case NetworkCapabilities.TRANSPORT_BLUETOOTH: - return ConnectivityManager.TYPE_BLUETOOTH; - case NetworkCapabilities.TRANSPORT_ETHERNET: - return ConnectivityManager.TYPE_ETHERNET; - default: - loge("Unexpected transport in transportTypeToLegacyType: " + type); - } - return ConnectivityManager.TYPE_NONE; - } /** * Ensures that the system cannot call a particular method. */ @@ -2274,20 +2321,6 @@ public class ConnectivityService extends IConnectivityManager.Stub sendStickyBroadcast(makeGeneralIntent(info, bcastType)); } - private void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) { - Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE); - intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType); - intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active); - intent.putExtra(ConnectivityManager.EXTRA_REALTIME_NS, tsNanos); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, - RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - private void sendStickyBroadcast(Intent intent) { synchronized (this) { if (!mSystemReady @@ -2393,74 +2426,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Setup data activity tracking for the given network. - * - * Every {@code setupDataActivityTracking} should be paired with a - * {@link #removeDataActivityTracking} for cleanup. - */ - private void setupDataActivityTracking(NetworkAgentInfo networkAgent) { - final String iface = networkAgent.linkProperties.getInterfaceName(); - - final int timeout; - final int type; - - if (networkAgent.networkCapabilities.hasTransport( - NetworkCapabilities.TRANSPORT_CELLULAR)) { - timeout = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE, - 10); - type = NetworkCapabilities.TRANSPORT_CELLULAR; - } else if (networkAgent.networkCapabilities.hasTransport( - NetworkCapabilities.TRANSPORT_WIFI)) { - timeout = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI, - 15); - type = NetworkCapabilities.TRANSPORT_WIFI; - } else { - return; // do not track any other networks - } - - if (timeout > 0 && iface != null) { - try { - mNMS.addIdleTimer(iface, timeout, type); - } catch (Exception e) { - // You shall not crash! - loge("Exception in setupDataActivityTracking " + e); - } - } - } - - /** - * Remove data activity tracking when network disconnects. - */ - private void removeDataActivityTracking(NetworkAgentInfo networkAgent) { - final String iface = networkAgent.linkProperties.getInterfaceName(); - final NetworkCapabilities caps = networkAgent.networkCapabilities; - - if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || - caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) { - try { - // the call fails silently if no idle timer setup for this interface - mNMS.removeIdleTimer(iface); - } catch (Exception e) { - loge("Exception in removeDataActivityTracking " + e); - } - } - } - - /** - * Update data activity tracking when network state is updated. - */ - private void updateDataActivityTracking(NetworkAgentInfo newNetwork, - NetworkAgentInfo oldNetwork) { - if (newNetwork != null) { - setupDataActivityTracking(newNetwork); - } - if (oldNetwork != null) { - removeDataActivityTracking(oldNetwork); - } - } - /** * Reads the network specific MTU size from resources. * and set it on it's iface. */ @@ -2874,13 +2839,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); break; } - final ArrayList<Network> underlying; - try { - underlying = ((Bundle) arg.second).getParcelableArrayList( - NetworkAgent.UNDERLYING_NETWORKS_KEY); - } catch (NullPointerException | ClassCastException e) { - break; - } + final List<Network> underlying = (List<Network>) arg.second; final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; nai.declaredUnderlyingNetworks = (underlying != null) ? underlying.toArray(new Network[0]) : null; @@ -2893,6 +2852,7 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilitiesForNetwork(nai); notifyIfacesChangedForNetworkStats(); } + break; } } } @@ -3454,6 +3414,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // of rematchAllNetworksAndRequests notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK); + + mQosCallbackTracker.handleNetworkReleased(nai.network); for (String iface : nai.linkProperties.getAllInterfaceNames()) { // Disable wakeup packet monitoring for each interface. wakeupModifyInterface(iface, nai.networkCapabilities, false); @@ -3484,7 +3446,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the default network disconnecting. Find out why, fix the rematch code, and delete this. if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { mDefaultNetworkNai = null; - updateDataActivityTracking(null /* newNetwork */, nai); + mNetworkActivityTracker.updateDataActivityTracking(null /* newNetwork */, nai); notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.toShortString()); } @@ -3723,7 +3685,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nri.unlinkDeathRecipient(); mNetworkRequests.remove(nri.request); - decrementNetworkRequestPerUidCount(nri); + mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); if (nri.request.isRequest()) { @@ -3796,19 +3758,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) { - synchronized (mUidToNetworkRequestCount) { - final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); - if (requests < 1) { - Log.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid); - } else if (requests == 1) { - mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid)); - } else { - mUidToNetworkRequestCount.put(nri.mUid, requests - 1); - } - } - } - @Override public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { enforceNetworkStackSettingsOrSetup(); @@ -4635,6 +4584,10 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.w(TAG, s); } + private static void logwtf(String s) { + Log.wtf(TAG, s); + } + private static void loge(String s) { Log.e(TAG, s); } @@ -5377,11 +5330,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); - private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; - // Map from UID to number of NetworkRequests that UID has filed. - @GuardedBy("mUidToNetworkRequestCount") - private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); - private static class NetworkProviderInfo { public final String name; public final Messenger messenger; @@ -5495,7 +5443,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); - enforceRequestCountLimit(); + mNetworkRequestCounter.incrementCountOrThrow(mUid); } NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) { @@ -5508,7 +5456,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPid = getCallingPid(); mUid = mDeps.getCallingUid(); mPendingIntent = null; - enforceRequestCountLimit(); + mNetworkRequestCounter.incrementCountOrThrow(mUid); try { mBinder.linkToDeath(this, 0); @@ -5545,17 +5493,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } - private void enforceRequestCountLimit() { - synchronized (mUidToNetworkRequestCount) { - int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1; - if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) { - throw new ServiceSpecificException( - ConnectivityManager.Errors.TOO_MANY_REQUESTS); - } - mUidToNetworkRequestCount.put(mUid, networkRequests); - } - } - void unlinkDeathRecipient() { if (mBinder != null) { mBinder.unlinkToDeath(this, 0); @@ -5777,9 +5714,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // Policy already enforced. return; } - if (mPolicyManagerInternal.isUidRestrictedOnMeteredNetworks(uid)) { - // If UID is restricted, don't allow them to bring up metered APNs. - networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + final long ident = Binder.clearCallingIdentity(); + try { + if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) { + // If UID is restricted, don't allow them to bring up metered APNs. + networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -6114,7 +6056,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), - this, mNetd, mDnsResolver, mNMS, providerId, uid); + this, mNetd, mDnsResolver, mNMS, providerId, uid, mQosCallbackTracker); // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. processCapabilitiesFromAgent(nai, nc); @@ -7314,7 +7256,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (oldDefaultNetwork != null) { mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } - updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); + mNetworkActivityTracker.updateDataActivityTracking( + newDefaultNetwork, oldDefaultNetwork); // Notify system services of the new default. makeDefault(newDefaultNetwork); @@ -7912,10 +7855,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId, + public void startNattKeepaliveWithFd(Network network, ParcelFileDescriptor pfd, int resourceId, int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr, String dstAddr) { try { + final FileDescriptor fd = pfd.getFileDescriptor(); mKeepaliveTracker.startNattKeepalive( getNetworkAgentInfoForNetwork(network), fd, resourceId, intervalSeconds, cb, @@ -7923,24 +7867,25 @@ public class ConnectivityService extends IConnectivityManager.Stub } finally { // FileDescriptors coming from AIDL calls must be manually closed to prevent leaks. // startNattKeepalive calls Os.dup(fd) before returning, so we can close immediately. - if (fd != null && Binder.getCallingPid() != Process.myPid()) { - IoUtils.closeQuietly(fd); + if (pfd != null && Binder.getCallingPid() != Process.myPid()) { + IoUtils.closeQuietly(pfd); } } } @Override - public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds, + public void startTcpKeepalive(Network network, ParcelFileDescriptor pfd, int intervalSeconds, ISocketKeepaliveCallback cb) { try { enforceKeepalivePermission(); + final FileDescriptor fd = pfd.getFileDescriptor(); mKeepaliveTracker.startTcpKeepalive( getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, cb); } finally { // FileDescriptors coming from AIDL calls must be manually closed to prevent leaks. // startTcpKeepalive calls Os.dup(fd) before returning, so we can close immediately. - if (fd != null && Binder.getCallingPid() != Process.myPid()) { - IoUtils.closeQuietly(fd); + if (pfd != null && Binder.getCallingPid() != Process.myPid()) { + IoUtils.closeQuietly(pfd); } } } @@ -8391,7 +8336,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Decrement the reference count for this NetworkRequestInfo. The reference count is // incremented when the NetworkRequestInfo is created as part of // enforceRequestCountLimit(). - decrementNetworkRequestPerUidCount(nri); + mNetworkRequestCounter.decrementCount(nri.mUid); return; } @@ -8457,7 +8402,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Decrement the reference count for this NetworkRequestInfo. The reference count is // incremented when the NetworkRequestInfo is created as part of // enforceRequestCountLimit(). - decrementNetworkRequestPerUidCount(nri); + mNetworkRequestCounter.decrementCount(nri.mUid); iCb.unlinkToDeath(cbInfo, 0); } @@ -8666,4 +8611,194 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyDataStallSuspected(p, network.getNetId()); } + + private final LegacyNetworkActivityTracker mNetworkActivityTracker; + + /** + * Class used for updating network activity tracking with netd and notify network activity + * changes. + */ + private static final class LegacyNetworkActivityTracker { + private final Context mContext; + private final INetworkManagementService mNMS; + + LegacyNetworkActivityTracker(@NonNull Context context, + @NonNull INetworkManagementService nms) { + mContext = context; + mNMS = nms; + 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); + } + }; + + // This is deprecated and only to support legacy use cases. + private int transportTypeToLegacyType(int type) { + switch (type) { + case NetworkCapabilities.TRANSPORT_CELLULAR: + return ConnectivityManager.TYPE_MOBILE; + case NetworkCapabilities.TRANSPORT_WIFI: + return ConnectivityManager.TYPE_WIFI; + case NetworkCapabilities.TRANSPORT_BLUETOOTH: + return ConnectivityManager.TYPE_BLUETOOTH; + case NetworkCapabilities.TRANSPORT_ETHERNET: + return ConnectivityManager.TYPE_ETHERNET; + default: + loge("Unexpected transport in transportTypeToLegacyType: " + type); + } + return ConnectivityManager.TYPE_NONE; + } + + public void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) { + final Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE); + intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType); + intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active); + intent.putExtra(ConnectivityManager.EXTRA_REALTIME_NS, tsNanos); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, + RECEIVE_DATA_ACTIVITY_CHANGE, + null /* resultReceiver */, + null /* scheduler */, + 0 /* initialCode */, + null /* initialData */, + null /* initialExtra */); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Setup data activity tracking for the given network. + * + * Every {@code setupDataActivityTracking} should be paired with a + * {@link #removeDataActivityTracking} for cleanup. + */ + private void setupDataActivityTracking(NetworkAgentInfo networkAgent) { + final String iface = networkAgent.linkProperties.getInterfaceName(); + + final int timeout; + final int type; + + if (networkAgent.networkCapabilities.hasTransport( + NetworkCapabilities.TRANSPORT_CELLULAR)) { + timeout = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE, + 10); + type = NetworkCapabilities.TRANSPORT_CELLULAR; + } else if (networkAgent.networkCapabilities.hasTransport( + NetworkCapabilities.TRANSPORT_WIFI)) { + timeout = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI, + 15); + type = NetworkCapabilities.TRANSPORT_WIFI; + } else { + return; // do not track any other networks + } + + if (timeout > 0 && iface != null) { + try { + // TODO: Access INetd directly instead of NMS + mNMS.addIdleTimer(iface, timeout, type); + } catch (Exception e) { + // You shall not crash! + loge("Exception in setupDataActivityTracking " + e); + } + } + } + + /** + * Remove data activity tracking when network disconnects. + */ + private void removeDataActivityTracking(NetworkAgentInfo networkAgent) { + final String iface = networkAgent.linkProperties.getInterfaceName(); + final NetworkCapabilities caps = networkAgent.networkCapabilities; + + if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + || caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) { + try { + // the call fails silently if no idle timer setup for this interface + // TODO: Access INetd directly instead of NMS + mNMS.removeIdleTimer(iface); + } catch (Exception e) { + // You shall not crash! + loge("Exception in removeDataActivityTracking " + e); + } + } + } + + /** + * Update data activity tracking when network state is updated. + */ + public void updateDataActivityTracking(NetworkAgentInfo newNetwork, + NetworkAgentInfo oldNetwork) { + if (newNetwork != null) { + setupDataActivityTracking(newNetwork); + } + if (oldNetwork != null) { + removeDataActivityTracking(oldNetwork); + } + } + } + /** + * Registers {@link QosSocketFilter} with {@link IQosCallback}. + * + * @param socketInfo the socket information + * @param callback the callback to register + */ + @Override + public void registerQosSocketCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final IQosCallback callback) { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(socketInfo.getNetwork()); + if (nai == null || nai.networkCapabilities == null) { + try { + callback.onError(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + } catch (final RemoteException ex) { + loge("registerQosCallbackInternal: RemoteException", ex); + } + return; + } + registerQosCallbackInternal(new QosSocketFilter(socketInfo), callback, nai); + } + + /** + * Register a {@link IQosCallback} with base {@link QosFilter}. + * + * @param filter the filter to register + * @param callback the callback to register + * @param nai the agent information related to the filter's network + */ + @VisibleForTesting + public void registerQosCallbackInternal(@NonNull final QosFilter filter, + @NonNull final IQosCallback callback, @NonNull final NetworkAgentInfo nai) { + if (filter == null) throw new IllegalArgumentException("filter must be non-null"); + if (callback == null) throw new IllegalArgumentException("callback must be non-null"); + + if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { + enforceConnectivityRestrictedNetworksPermission(); + } + mQosCallbackTracker.registerCallback(callback, filter, nai); + } + + /** + * Unregisters the given callback. + * + * @param callback the callback to unregister + */ + @Override + public void unregisterQosCallback(@NonNull final IQosCallback callback) { + mQosCallbackTracker.unregisterCallback(callback); + } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 1ea4a89a761f..d30a6405e95d 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -405,6 +405,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (mLastPowerStateFromRadio != powerState) { mLastPowerStateFromRadio = powerState; try { + // TODO: The interface changes that comes from netd are handled by BSS itself. + // There are still events caused by setting or removing idle timer, so keep + // reporting from here until setting idler timer moved to CS. getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); } catch (RemoteException e) { } @@ -415,6 +418,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (mLastPowerStateFromWifi != powerState) { mLastPowerStateFromWifi = powerState; try { + // TODO: The interface changes that comes from netd are handled by BSS itself. + // There are still events caused by setting or removing idle timer, so keep + // reporting from here until setting idler timer moved to CS. getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); } catch (RemoteException e) { } diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index e99bb245a2e0..0aee78050929 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -16,10 +16,14 @@ package com.android.server; +import static android.app.ActivityManager.RunningServiceInfo; +import static android.app.ActivityManager.RunningTaskInfo; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_CAMERA; 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.os.UserHandle.USER_SYSTEM; import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; @@ -28,15 +32,20 @@ import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.graphics.drawable.Icon; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; @@ -55,10 +64,12 @@ import android.os.UserHandle; import android.service.SensorPrivacyIndividualEnabledSensorProto; import android.service.SensorPrivacyServiceDumpProto; import android.service.SensorPrivacyUserProto; +import android.text.Html; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TypedXmlPullParser; @@ -66,6 +77,7 @@ import android.util.TypedXmlSerializer; import android.util.Xml; import android.util.proto.ProtoOutputStream; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; @@ -83,6 +95,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; @@ -91,6 +105,8 @@ public final class SensorPrivacyService extends SystemService { private static final String TAG = "SensorPrivacyService"; + private static final int SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000; + /** Version number indicating compatibility parsing the persisted file */ private static final int CURRENT_PERSISTENCE_VERSION = 1; /** Version number indicating the persisted data needs upgraded to match new internal data @@ -110,8 +126,6 @@ public final class SensorPrivacyService extends SystemService { private static final String SENSOR_PRIVACY_CHANNEL_ID = Context.SENSOR_PRIVACY_SERVICE; private static final String ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY = SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy"; - private static final String EXTRA_SENSOR = SensorPrivacyService.class.getName() - + ".extra.sensor"; // These are associated with fields that existed for older persisted versions of files private static final int VER0_ENABLED = 0; @@ -121,11 +135,15 @@ public final class SensorPrivacyService extends SystemService { private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; private final UserManagerInternal mUserManagerInternal; + private final ActivityManager mActivityManager; + private final ActivityTaskManager mActivityTaskManager; public SensorPrivacyService(Context context) { super(context); mUserManagerInternal = getLocalService(UserManagerInternal.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); + mActivityManager = context.getSystemService(ActivityManager.class); + mActivityTaskManager = context.getSystemService(ActivityTaskManager.class); } @Override @@ -134,7 +152,8 @@ public final class SensorPrivacyService extends SystemService { } class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements - AppOpsManager.OnOpNotedListener, AppOpsManager.OnOpStartedListener { + AppOpsManager.OnOpNotedListener, AppOpsManager.OnOpStartedListener, + IBinder.DeathRecipient { private final SensorPrivacyHandler mHandler; private final Context mContext; @@ -146,6 +165,15 @@ public final class SensorPrivacyService extends SystemService { @GuardedBy("mLock") private SparseArray<SparseBooleanArray> mIndividualEnabled = new SparseArray<>(); + /** + * Packages for which not to show sensor use reminders. + * + * <Package, User> -> list of suppressor tokens + */ + @GuardedBy("mLock") + private ArrayMap<Pair<String, UserHandle>, ArrayList<IBinder>> mSuppressReminders = + new ArrayMap<>(); + SensorPrivacyServiceImpl(Context context) { mContext = context; mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext); @@ -166,7 +194,9 @@ public final class SensorPrivacyService extends SystemService { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - setIndividualSensorPrivacy(intent.getIntExtra(Intent.EXTRA_USER_ID, -1), + setIndividualSensorPrivacy( + ((UserHandle) intent.getParcelableExtra( + Intent.EXTRA_USER)).getIdentifier(), intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); } }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)); @@ -192,7 +222,12 @@ public final class SensorPrivacyService extends SystemService { sensor = CAMERA; } - onSensorUseStarted(uid, packageName, sensor); + long token = Binder.clearCallingIdentity(); + try { + onSensorUseStarted(uid, packageName, sensor); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -203,31 +238,138 @@ public final class SensorPrivacyService extends SystemService { * @param sensor The sensor that is attempting to be used */ private void onSensorUseStarted(int uid, String packageName, int sensor) { - int userId = UserHandle.getUserId(uid); - if (!isIndividualSensorPrivacyEnabled(userId, sensor)) { + UserHandle user = UserHandle.getUserHandleForUid(uid); + if (!isIndividualSensorPrivacyEnabled(user.getIdentifier(), sensor)) { return; } - // TODO moltmann: Use dialog instead of notification if we can determine the activity - // which triggered this usage + synchronized (mLock) { + if (mSuppressReminders.containsKey(new Pair<>(packageName, user))) { + Log.d(TAG, + "Suppressed sensor privacy reminder for " + packageName + "/" + user); + return; + } + } + + // TODO: Handle reminders with multiple sensors + + // - If we have a likely activity that triggered the sensor use overlay a dialog over + // it. This should be the most common case. + // - If there is no use visible entity that triggered the sensor don't show anything as + // this is - from the point of the user - a background usage + // - Otherwise show a notification as we are not quite sure where to display the dialog. - // TODO evanseverson: - Implement final UX for notification - // - Finalize strings and icons and add as resources + List<RunningTaskInfo> tasksOfPackageUsingSensor = new ArrayList<>(); + + List<RunningTaskInfo> tasks = mActivityTaskManager.getTasks(Integer.MAX_VALUE); + int numTasks = tasks.size(); + for (int taskNum = 0; taskNum < numTasks; taskNum++) { + RunningTaskInfo task = tasks.get(taskNum); + + if (task.isVisible && task.topActivity.getPackageName().equals(packageName)) { + if (task.isFocused) { + // There is the one focused activity + showSensorUseReminderDialog(task.taskId, user, packageName, sensor); + return; + } + + tasksOfPackageUsingSensor.add(task); + } + } + + // TODO: Test this case + // There is one or more non-focused activity + if (tasksOfPackageUsingSensor.size() == 1) { + showSensorUseReminderDialog(tasksOfPackageUsingSensor.get(0).taskId, user, + packageName, sensor); + return; + } else if (tasksOfPackageUsingSensor.size() > 1) { + showSensorUseReminderNotification(user, packageName, sensor); + return; + } + + // TODO: Test this case + // Check if there is a foreground service for this package + List<RunningServiceInfo> services = mActivityManager.getRunningServices( + Integer.MAX_VALUE); + int numServices = services.size(); + for (int serviceNum = 0; serviceNum < numServices; serviceNum++) { + RunningServiceInfo service = services.get(serviceNum); + + if (service.foreground && service.service.getPackageName().equals(packageName)) { + showSensorUseReminderNotification(user, packageName, sensor); + return; + } + } + + Log.i(TAG, packageName + "/" + uid + " started using sensor " + sensor + + " but no activity or foreground service was running. The user will not be" + + " informed. System components should check if sensor privacy is enabled for" + + " the sensor before accessing it."); + } + + /** + * Show a dialog that informs the user that a sensor use or a blocked sensor started. + * The user can then react to this event. + * + * @param taskId The task this dialog should be overlaid on. + * @param user The user of the package using the sensor. + * @param packageName The name of the package using the sensor. + * @param sensor The sensor that is being used. + */ + private void showSensorUseReminderDialog(int taskId, @NonNull UserHandle user, + @NonNull String packageName, int sensor) { + Intent dialogIntent = new Intent(); + dialogIntent.setComponent(ComponentName.unflattenFromString( + mContext.getResources().getString( + R.string.config_sensorUseStartedActivity))); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskId(taskId); + options.setTaskOverlay(true, true); + + dialogIntent.putExtra(EXTRA_PACKAGE_NAME, packageName); + dialogIntent.putExtra(EXTRA_SENSOR, sensor); + + mContext.startActivityAsUser(dialogIntent, options.toBundle(), user); + } + + /** + * Show a notification that informs the user that a sensor use or a blocked sensor started. + * The user can then react to this event. + * + * @param user The user of the package using the sensor. + * @param packageName The name of the package using the sensor. + * @param sensor The sensor that is being used. + */ + private void showSensorUseReminderNotification(@NonNull UserHandle user, + @NonNull String packageName, int sensor) { + int iconRes; + int messageRes; + + CharSequence packageLabel; + try { + packageLabel = getUiContext().getPackageManager() + .getApplicationInfoAsUser(packageName, 0, user) + .loadLabel(mContext.getPackageManager()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Cannot show sensor use notification for " + packageName); + return; + } - int icon; - CharSequence notificationMessage; if (sensor == MICROPHONE) { - icon = com.android.internal.R.drawable.ic_mic; - notificationMessage = "Microphone is muted because of sensor privacy"; + iconRes = R.drawable.ic_mic_blocked; + messageRes = R.string.sensor_privacy_start_use_mic_notification_content; } else { - icon = com.android.internal.R.drawable.ic_camera; - notificationMessage = "Camera is blocked because of sensor privacy"; + iconRes = R.drawable.ic_camera_blocked; + messageRes = R.string.sensor_privacy_start_use_camera_notification_content; } NotificationManager notificationManager = mContext.getSystemService(NotificationManager.class); NotificationChannel channel = new NotificationChannel( - SENSOR_PRIVACY_CHANNEL_ID, "Sensor privacy", + SENSOR_PRIVACY_CHANNEL_ID, + getUiContext().getString(R.string.sensor_privacy_notification_channel_label), NotificationManager.IMPORTANCE_HIGH); channel.setSound(null, null); channel.setBypassDnd(true); @@ -236,18 +378,21 @@ public final class SensorPrivacyService extends SystemService { notificationManager.createNotificationChannel(channel); + Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes); notificationManager.notify(sensor, new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID) - .setContentTitle(notificationMessage) + .setContentTitle(Html.fromHtml(getUiContext().getString(messageRes, + packageLabel),0)) .setSmallIcon(icon) - .addAction(new Notification.Action.Builder( - Icon.createWithResource(mContext, icon), - "Disable sensor privacy", + .setLargeIcon(icon) + .addAction(new Notification.Action.Builder(icon, + getUiContext().getString( + R.string.sensor_privacy_start_use_dialog_turn_on_button), PendingIntent.getBroadcast(mContext, sensor, new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY) .setPackage(mContext.getPackageName()) .putExtra(EXTRA_SENSOR, sensor) - .putExtra(Intent.EXTRA_USER_ID, userId), + .putExtra(Intent.EXTRA_USER, user), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)) .build()) @@ -587,6 +732,88 @@ public final class SensorPrivacyService extends SystemService { } @Override + public void suppressIndividualSensorPrivacyReminders(int userId, String packageName, + IBinder token, boolean suppress) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(token); + + Pair<String, UserHandle> key = new Pair<>(packageName, UserHandle.of(userId)); + + synchronized (mLock) { + if (suppress) { + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(TAG, "Could not suppress sensor use reminder", e); + return; + } + + ArrayList<IBinder> suppressPackageReminderTokens = mSuppressReminders.get(key); + if (suppressPackageReminderTokens == null) { + suppressPackageReminderTokens = new ArrayList<>(1); + mSuppressReminders.put(key, suppressPackageReminderTokens); + } + + suppressPackageReminderTokens.add(token); + } else { + mHandler.postDelayed(PooledLambda.obtainRunnable( + SensorPrivacyServiceImpl::removeSuppressPackageReminderToken, + this, key, token), + SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS); + } + } + } + + /** + * Remove a sensor use reminder suppression token. + * + * @param key Key the token is in + * @param token The token to remove + */ + private void removeSuppressPackageReminderToken(@NonNull Pair<String, UserHandle> key, + @NonNull IBinder token) { + synchronized (mLock) { + ArrayList<IBinder> suppressPackageReminderTokens = + mSuppressReminders.get(key); + if (suppressPackageReminderTokens == null) { + Log.e(TAG, "No tokens for " + key); + return; + } + + boolean wasRemoved = suppressPackageReminderTokens.remove(token); + if (wasRemoved) { + token.unlinkToDeath(this, 0); + + if (suppressPackageReminderTokens.isEmpty()) { + mSuppressReminders.remove(key); + } + } else { + Log.w(TAG, "Could not remove sensor use reminder suppression token " + token + + " from " + key); + } + } + } + + /** + * A owner of a suppressor token died. Clean up. + * + * @param token The token that is invalid now. + */ + @Override + public void binderDied(@NonNull IBinder token) { + synchronized (mLock) { + for (Pair<String, UserHandle> key : mSuppressReminders.keySet()) { + removeSuppressPackageReminderToken(key, token); + } + } + } + + @Override + public void binderDied() { + // Handled in binderDied(IBinder) + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { Objects.requireNonNull(fd); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 2c83da55f275..a6a00fbe17ab 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -129,8 +129,10 @@ public class VibratorService extends IVibratorService.Stub { Slog.d(TAG, "Vibration thread finished with status " + status); } synchronized (mLock) { - mThread = null; - reportFinishVibrationLocked(status); + if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { + mThread = null; + reportFinishVibrationLocked(status); + } } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index aada21dfcc1e..f72c9fbf1a1b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -180,6 +180,7 @@ 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; @@ -303,6 +304,7 @@ import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessMap; import com.android.internal.app.SystemUserHomeActivity; 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; @@ -14351,6 +14353,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (disableHiddenApiChecks || disableTestApiChecks) { enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, "disable hidden API checks"); + + enableTestApiAccess(ii.packageName); } final long origId = Binder.clearCallingIdentity(); @@ -14518,6 +14522,25 @@ 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, diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index ada7eeab9da3..a3fac0510f20 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -593,7 +593,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } if (modemInfo != null) { - mStats.updateMobileRadioState(modemInfo, elapsedRealtime, uptime); + mStats.noteModemControllerActivity(modemInfo, elapsedRealtime, uptime); } if (updateFlags == UPDATE_ALL) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4fb7abbd1356..b1cbb4acdb13 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -21,6 +21,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.INetworkManagementEventObserver; +import android.net.NetworkCapabilities; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; @@ -29,6 +31,7 @@ import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFormatException; @@ -36,6 +39,7 @@ import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; @@ -54,6 +58,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; @@ -68,6 +73,7 @@ import com.android.internal.util.ParseUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.Watchdog; +import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; import java.io.File; @@ -123,6 +129,40 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final Handler mHandler; private final Object mLock = new Object(); + @GuardedBy("mStats") + private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + @GuardedBy("mStats") + private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + private final INetworkManagementEventObserver mActivityChangeObserver = + new BaseNetworkObserver() { + @Override + public void interfaceClassDataActivityChanged(int transportType, boolean active, + long tsNanos, int uid) { + final int powerState = active + ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + final long timestampNanos; + if (tsNanos <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = tsNanos; + } + + switch (transportType) { + case NetworkCapabilities.TRANSPORT_CELLULAR: + noteMobileRadioPowerState(powerState, timestampNanos, uid); + break; + case NetworkCapabilities.TRANSPORT_WIFI: + noteWifiRadioPowerState(powerState, timestampNanos, uid); + break; + default: + Slog.d(TAG, "Received unexpected transport in " + + "interfaceClassDataActivityChanged unexpected type: " + + transportType); + } + } + }; + /** * Replaces the information in the given rpmStats with up-to-date information. */ @@ -225,6 +265,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void systemServicesReady() { mStats.systemServicesReady(mContext); mWorker.systemServicesReady(); + final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + try { + nms.registerObserver(mActivityChangeObserver); + } catch (RemoteException e) { + Slog.e(TAG, "Could not register INetworkManagement event observer " + e); + } Watchdog.getInstance().addMonitor(this); } @@ -1040,6 +1087,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { final boolean update; synchronized (mStats) { + // Ignore if no power state change. + if (mLastPowerStateFromRadio == powerState) return; + + mLastPowerStateFromRadio = powerState; update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid, elapsedRealtime, uptime); } @@ -1343,6 +1394,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub // There was a change in WiFi power state. // Collect data now for the past activity. synchronized (mStats) { + // Ignore if no power state change. + if (mLastPowerStateFromWifi == powerState) return; + + mLastPowerStateFromWifi = powerState; if (mStats.isOnBattery()) { final String type = (powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH @@ -1798,7 +1853,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { - mStats.updateMobileRadioState(info, elapsedRealtime, uptime); + mStats.noteModemControllerActivity(info, elapsedRealtime, uptime); }); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 9eb7c07baaed..dd09a1c85682 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -133,7 +133,7 @@ public final class CachedAppOptimizer { static final int REPORT_UNFREEZE_MSG = 4; //TODO:change this static definition into a configurable flag. - static final int FREEZE_TIMEOUT_MS = 10000; + static final long FREEZE_TIMEOUT_MS = 600000; static final int DO_FREEZE = 1; static final int REPORT_UNFREEZE = 2; diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java index f20b3a18dc6d..60b246726593 100644 --- a/services/core/java/com/android/server/am/PreBootBroadcaster.java +++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java @@ -110,8 +110,12 @@ public abstract class PreBootBroadcaster extends IIntentReceiver.Stub { EventLogTags.writeAmPreBoot(mUserId, componentName.getPackageName()); mIntent.setComponent(componentName); - final long duration = LocalServices.getService(ActivityManagerInternal.class) - .getBootTimeTempAllowListDuration(); + long duration = 10_000; + final ActivityManagerInternal amInternal = + LocalServices.getService(ActivityManagerInternal.class); + if (amInternal != null) { + duration = amInternal.getBootTimeTempAllowListDuration(); + } final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); bOptions.setTemporaryAppWhitelistDuration( BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 47b7e1b5fa41..2273779c2127 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -340,12 +340,23 @@ public final class ProcessList { private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. /** - * Enable memory tag checks in non-system apps. This flag will only have an effect on - * hardware supporting the ARM Memory Tagging Extension (MTE). + * Enable asynchronous (ASYNC) memory tag checking in this process. This + * flag will only have an effect on hardware supporting the ARM Memory + * Tagging Extension (MTE). */ @ChangeId @Disabled - private static final long NATIVE_MEMORY_TAGGING = 135772972; // This is a bug id. + private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id. + + /** + * Enable synchronous (SYNC) memory tag checking in this process. This flag + * will only have an effect on hardware supporting the ARM Memory Tagging + * Extension (MTE). If both NATIVE_MEMTAG_ASYNC and this option is selected, + * this option takes preference and MTE is enabled in SYNC mode. + */ + @ChangeId + @Disabled + private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id. /** * Enable sampled memory bug detection in the app. @@ -1655,23 +1666,23 @@ public final class ProcessList { return gidArray; } - private boolean shouldEnableMemoryTagging(ProcessRecord app) { + // Returns the memory tagging level to be enabled. If memory tagging isn't + // requested, returns zero. + private int getMemtagLevel(ProcessRecord app) { // Ensure the hardware + kernel actually supports MTE. if (!Zygote.nativeSupportsMemoryTagging()) { - return false; + return 0; } - // Enable MTE for system apps if supported. - if ((app.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - return true; + if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) { + return Zygote.MEMORY_TAG_LEVEL_SYNC; } - // Enable MTE if the compat feature is enabled. - if (mPlatformCompat.isChangeEnabled(NATIVE_MEMORY_TAGGING, app.info)) { - return true; + if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_ASYNC, app.info)) { + return Zygote.MEMORY_TAG_LEVEL_ASYNC; } - return false; + return 0; } private boolean shouldEnableTaggedPointers(ProcessRecord app) { @@ -1695,8 +1706,9 @@ public final class ProcessList { private int decideTaggingLevel(ProcessRecord app) { // Check MTE support first, as it should take precedence over TBI. - if (shouldEnableMemoryTagging(app)) { - return Zygote.MEMORY_TAG_LEVEL_ASYNC; + int memtagLevel = getMemtagLevel(app); + if (memtagLevel != 0) { + return memtagLevel; } if (shouldEnableTaggedPointers(app)) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 6d90eaafcf77..ffe1d68c0583 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2811,8 +2811,12 @@ class UserController implements Handler.Callback { } private BroadcastOptions getTemporaryAppWhitelistBroadcastOptions() { - final long duration = LocalServices.getService(ActivityManagerInternal.class) - .getBootTimeTempAllowListDuration(); + long duration = 10_000; + final ActivityManagerInternal amInternal = + LocalServices.getService(ActivityManagerInternal.class); + if (amInternal != null) { + duration = amInternal.getBootTimeTempAllowListDuration(); + } final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); bOptions.setTemporaryAppWhitelistDuration( BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 26f5c4ca1e80..9aea7c4c6dad 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -154,6 +154,8 @@ import java.util.concurrent.atomic.AtomicBoolean; mPreferredDeviceforComm = null; initCommunicationStrategyId(); + + mSystemServer.registerUserStartedReceiver(mContext); } /*package*/ Context getContext() { @@ -993,6 +995,10 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + /*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) { + mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent); + } + /*package*/ void dump(PrintWriter pw, String prefix) { if (mBrokerHandler != null) { pw.println(prefix + "Message handler (watch for unhandled messages):"); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 82586b8f9b23..076cbffc0a29 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -16,7 +16,6 @@ package com.android.server.audio; import android.annotation.NonNull; -import android.app.ActivityManager; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -37,7 +36,6 @@ import android.media.MediaMetrics; import android.os.Binder; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -1270,7 +1268,7 @@ public class AudioDeviceInventory { final long ident = Binder.clearCallingIdentity(); try { - ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT); + mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 7115c9ad5a05..c94b48c13fe2 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -572,9 +572,9 @@ public class AudioService extends IAudioService.Stub // Pre-scale for Bluetooth Absolute Volume private float[] mPrescaleAbsoluteVolume = new float[] { - 0.5f, // Pre-scale for index 1 - 0.7f, // Pre-scale for index 2 - 0.85f, // Pre-scale for index 3 + 0.6f, // Pre-scale for index 1 + 0.8f, // Pre-scale for index 2 + 0.9f, // Pre-scale for index 3 }; private NotificationManager mNm; @@ -5591,7 +5591,9 @@ public class AudioService extends IAudioService.Stub profile, suppressNoisyIntent, a2dpVolume); } - /*package*/ void setMusicMute(boolean mute) { + /** only public for mocking/spying, do not call outside of AudioService */ + @VisibleForTesting + public void setMusicMute(boolean mute) { mStreamStates[AudioSystem.STREAM_MUSIC].muteInternally(mute); } @@ -7071,7 +7073,9 @@ public class AudioService extends IAudioService.Stub } } - /*package*/ void checkMusicActive(int deviceType, String caller) { + /** only public for mocking/spying, do not call outside of AudioService */ + @VisibleForTesting + public void checkMusicActive(int deviceType, String caller) { if (mSafeMediaVolumeDevices.contains(deviceType)) { sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 5025c4aeaf05..b1633b01df90 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -288,6 +288,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { @GuardedBy("mAudioFocusLock") private void removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers) { + AudioFocusInfo abandonSource = null; // is the current top of the focus stack abandoning focus? (because of request, not death) if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) { @@ -295,9 +296,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { FocusRequester fr = mFocusStack.pop(); fr.release(); if (notifyFocusFollowers) { - final AudioFocusInfo afi = fr.toAudioFocusInfo(); - afi.clearLossReceived(); - notifyExtPolicyFocusLoss_syncAf(afi, false); + abandonSource = fr.toAudioFocusInfo(); } if (signal) { // notify the new top of the stack it gained focus @@ -315,11 +314,19 @@ public class MediaFocusControl implements PlayerFocusEnforcer { Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + clientToRemove); stackIterator.remove(); + if (notifyFocusFollowers) { + abandonSource = fr.toAudioFocusInfo(); + } // stack entry not used anymore, clear references fr.release(); } } } + // focus followers still want to know focus was abandoned, handled as a loss + if (abandonSource != null) { + abandonSource.clearLossReceived(); + notifyExtPolicyFocusLoss_syncAf(abandonSource, false); + } if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) { Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator(); diff --git a/services/core/java/com/android/server/audio/SystemServerAdapter.java b/services/core/java/com/android/server/audio/SystemServerAdapter.java index 68893f8ca6c7..22456bcf3f66 100644 --- a/services/core/java/com/android/server/audio/SystemServerAdapter.java +++ b/services/core/java/com/android/server/audio/SystemServerAdapter.java @@ -18,11 +18,20 @@ package com.android.server.audio; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; import android.media.AudioManager; import android.os.Binder; import android.os.UserHandle; +import android.os.UserManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.util.Objects; @@ -82,4 +91,58 @@ public class SystemServerAdapter { Binder.restoreCallingIdentity(ident); } } + + /** + * Send sticky broadcast to current user's profile group (including current user) + */ + @VisibleForTesting + public void broadcastStickyIntentToCurrentProfileGroup(Intent intent) { + int[] profileIds = LocalServices.getService( + ActivityManagerInternal.class).getCurrentProfileIds(); + for (int userId : profileIds) { + ActivityManager.broadcastStickyIntent(intent, userId); + } + } + + /** + * Broadcast sticky intents when a profile is started. This is needed because newly created + * profiles would not receive the intents until the next state change. + */ + /*package*/ void registerUserStartedReceiver(Context context) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_STARTED); + context.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + return; + } + + UserManager userManager = context.getSystemService(UserManager.class); + final UserInfo profileParent = userManager.getProfileParent(userId); + if (profileParent == null) { + return; + } + + // get sticky intents from parent and broadcast them to the started profile + broadcastProfileParentStickyIntent(context, AudioManager.ACTION_HDMI_AUDIO_PLUG, + userId, profileParent.id); + broadcastProfileParentStickyIntent(context, AudioManager.ACTION_HEADSET_PLUG, + userId, profileParent.id); + } + } + }, UserHandle.ALL, filter, null, null); + } + + private void broadcastProfileParentStickyIntent(Context context, String intentAction, + int profileId, int parentId) { + Intent intent = context.registerReceiverAsUser(/*receiver*/ null, UserHandle.of(parentId), + new IntentFilter(intentAction), /*broadcastPermission*/ null, /*scheduler*/ null); + if (intent != null) { + ActivityManager.broadcastStickyIntent(intent, profileId); + } + } } diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 52152ab78992..14292d9c5f8d 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -37,7 +37,6 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.IBinder; import android.os.RemoteException; @@ -62,8 +61,6 @@ public final class AuthSession implements IBinder.DeathRecipient { private static final String TAG = "BiometricService/AuthSession"; private static final boolean DEBUG = false; - - /* * Defined in biometrics.proto */ diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 3387049d69f3..614c5f1b65bf 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -815,12 +815,16 @@ public class BiometricService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { if (args.length > 0 && "--proto".equals(args[0])) { + final boolean clearSchedulerBuffer = args.length > 1 + && "--clear-scheduler-buffer".equals(args[1]); + Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer); final ProtoOutputStream proto = new ProtoOutputStream(fd); proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE, mCurrentAuthSession != null ? mCurrentAuthSession.getState() : STATE_AUTH_IDLE); for (BiometricSensor sensor : mSensors) { - byte[] serviceState = sensor.impl.dumpSensorServiceStateProto(); + byte[] serviceState = sensor.impl + .dumpSensorServiceStateProto(clearSchedulerBuffer); proto.write(BiometricServiceStateProto.SENSOR_SERVICE_STATES, serviceState); } proto.flush(); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 5663495db037..3f6ae6439aa4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -33,6 +33,7 @@ import android.security.KeyStore; import android.util.EventLog; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.Utils; import java.util.ArrayList; @@ -298,4 +299,9 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_AUTHENTICATE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index f3c37efd4b61..8fa3bbbf615a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -24,6 +24,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import java.util.NoSuchElementException; /** @@ -80,6 +82,12 @@ public abstract class BaseClientMonitor extends LoggableMonitor @NonNull protected Callback mCallback; /** + * Returns a ClientMonitorEnum constant defined in biometrics.proto + * @return + */ + public abstract int getProtoEnum(); + + /** * @param context system_server context * @param token a unique token for the client * @param listener recipient of related events (e.g. authentication) @@ -195,10 +203,16 @@ public abstract class BaseClientMonitor extends LoggableMonitor return mSensorId; } + @VisibleForTesting + public Callback getCallback() { + return mCallback; + } + @Override public String toString() { return "{[" + mSequentialId + "] " + this.getClass().getSimpleName() + + ", " + getProtoEnum() + ", " + getOwnerString() + ", " + getCookie() + "}"; } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index aa7faf51b1b6..c86bfcb4a88e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -28,8 +28,11 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.BiometricSchedulerProto; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.io.PrintWriter; @@ -51,6 +54,8 @@ import java.util.Locale; public class BiometricScheduler { private static final String BASE_TAG = "BiometricScheduler"; + // Number of recent operations to keep in our logs for dumpsys + private static final int LOG_NUM_RECENT_OPERATIONS = 50; /** * Contains all the necessary information for a HAL operation. @@ -200,6 +205,10 @@ public class BiometricScheduler { @VisibleForTesting @Nullable Operation mCurrentOperation; @NonNull private final ArrayDeque<CrashState> mCrashStates; + private int mTotalOperationsHandled; + private final int mRecentOperationsLimit; + @NonNull private final List<Integer> mRecentOperations; + // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as // starting the next client). @@ -240,7 +249,12 @@ public class BiometricScheduler { mCurrentOperation.mClientMonitor.getSensorId(), false /* active */); } + if (mRecentOperations.size() >= mRecentOperationsLimit) { + mRecentOperations.remove(0); + } + mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum()); mCurrentOperation = null; + mTotalOperationsHandled++; startNextOperationIfIdle(); }); } @@ -249,13 +263,15 @@ public class BiometricScheduler { @VisibleForTesting BiometricScheduler(@NonNull String tag, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - @NonNull IBiometricService biometricService) { + @NonNull IBiometricService biometricService, int recentOperationsLimit) { mBiometricTag = tag; mInternalCallback = new InternalCallback(); mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; mPendingOperations = new ArrayDeque<>(); mBiometricService = biometricService; mCrashStates = new ArrayDeque<>(); + mRecentOperationsLimit = recentOperationsLimit; + mRecentOperations = new ArrayList<>(); } /** @@ -267,7 +283,7 @@ public class BiometricScheduler { public BiometricScheduler(@NonNull String tag, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE))); + ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS); } /** @@ -602,6 +618,24 @@ public class BiometricScheduler { } } + public byte[] dumpProtoState(boolean clearSchedulerBuffer) { + final ProtoOutputStream proto = new ProtoOutputStream(); + proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null + ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE); + proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled); + Slog.d(getTag(), "Total operations: " + mTotalOperationsHandled); + for (int i = 0; i < mRecentOperations.size(); i++) { + Slog.d(getTag(), "Operation: " + mRecentOperations.get(i)); + proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i)); + } + proto.flush(); + + if (clearSchedulerBuffer) { + mRecentOperations.clear(); + } + return proto.getBytes(); + } + /** * Clears the scheduler of anything work-related. This should be used for example when the * HAL dies. diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java index 61e7c8922f39..da76af800d3d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java @@ -160,4 +160,18 @@ public class ClientMonitorCallbackConverter { mFaceServiceReceiver.onChallengeInterruptFinished(sensorId); } } + + // Fingerprint-specific callbacks for FingerprintManager only + + public void onUdfpsPointerDown(int sensorId, int cookie) throws RemoteException { + if (mFingerprintServiceReceiver != null) { + mFingerprintServiceReceiver.onUdfpsPointerDown(sensorId); + } + } + + public void onUdfpsPointerUp(int sensorId, int cookie) throws RemoteException { + if (mFingerprintServiceReceiver != null) { + mFingerprintServiceReceiver.onUdfpsPointerUp(sensorId); + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 8bf9680d60cd..8d81016dab59 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -24,6 +24,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; + import java.util.Arrays; /** @@ -106,4 +108,9 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> { false /* enrollSuccessful */); super.onError(error, vendorCode); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_ENROLL; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java index 6a622c339d0b..741946e147cd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; + public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> { private static final String TAG = "GenerateChallengeClient"; @@ -50,4 +52,9 @@ public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> { startHalOperation(); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_GENERATE_CHALLENGE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 8529e810f9a1..ce24e5efdc07 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; import java.util.List; @@ -166,4 +167,9 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide } ((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_INTERNAL_CLEANUP; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 2693f2f0e2de..9d19fdf4868d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; import java.util.List; @@ -123,4 +124,9 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> public List<BiometricAuthenticator.Identifier> getUnknownHALTemplates() { return mUnknownHALTemplates; } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_ENUMERATE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java index 630e5eaae377..cede4a725246 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java @@ -24,6 +24,8 @@ import android.hardware.biometrics.IInvalidationCallback; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; + import java.util.Map; /** @@ -70,4 +72,9 @@ public abstract class InvalidationClient<S extends BiometricAuthenticator.Identi public void unableToStart() { } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_INVALIDATE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java index c97003b875ed..5ba1b0000a7c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java @@ -23,6 +23,8 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IInvalidationCallback; +import com.android.server.biometrics.BiometricsProto; + /** * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other * sensors. See {@link InvalidationClient} for the ClientMonitor subclass responsible for initiating @@ -89,4 +91,9 @@ public class InvalidationRequesterClient<S extends BiometricAuthenticator.Identi mBiometricManager.invalidateAuthenticatorIds(getTargetUserId(), getSensorId(), mInvalidationCallback); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_INVALIDATION_REQUESTER; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java index 3ca069100fbe..edde3d496e7c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java @@ -67,10 +67,23 @@ public abstract class LoggableMonitor { return mStatsClient; } - private boolean isAnyFieldUnknown() { - return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN - || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN - || mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN; + private boolean shouldSkipLogging() { + boolean shouldSkipLogging = (mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN + || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN); + + if (mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN) { + Slog.w(TAG, "Unknown field detected: MODALITY_UNKNOWN, will not report metric"); + } + + if (mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN) { + Slog.w(TAG, "Unknown field detected: ACTION_UNKNOWN, will not report metric"); + } + + if (mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN) { + Slog.w(TAG, "Unknown field detected: CLIENT_UNKNOWN"); + } + + return shouldSkipLogging; } protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, @@ -101,7 +114,7 @@ public abstract class LoggableMonitor { + ", VendorCode: " + vendorCode); } - if (isAnyFieldUnknown()) { + if (shouldSkipLogging()) { return; } @@ -138,7 +151,7 @@ public abstract class LoggableMonitor { Slog.v(TAG, "Error latency: " + latency); } - if (isAnyFieldUnknown()) { + if (shouldSkipLogging()) { return; } @@ -189,7 +202,7 @@ public abstract class LoggableMonitor { Slog.v(TAG, "Authentication latency: " + latency); } - if (isAnyFieldUnknown()) { + if (shouldSkipLogging()) { return; } @@ -219,7 +232,7 @@ public abstract class LoggableMonitor { Slog.v(TAG, "Enroll latency: " + latency); } - if (isAnyFieldUnknown()) { + if (shouldSkipLogging()) { return; } diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java index 4ea48fdf05a0..e0626952fac3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java @@ -25,6 +25,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; + import java.util.Map; /** @@ -90,4 +92,9 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, mCallback.onClientFinished(this, true /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_REMOVE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java index 187193dcd50a..90fa1b4ad1d6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java @@ -21,6 +21,8 @@ import android.content.Context; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; +import com.android.server.biometrics.BiometricsProto; + public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> { public RevokeChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @@ -42,4 +44,9 @@ public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> { startHalOperation(); mCallback.onClientFinished(this, true /* success */); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_REVOKE_CHALLENGE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java index 54ab2e564676..f37cf18a1320 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java @@ -52,8 +52,8 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub { } @Override - public byte[] dumpSensorServiceStateProto() throws RemoteException { - return mFaceService.dumpSensorServiceStateProto(mSensorId); + public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException { + return mFaceService.dumpSensorServiceStateProto(mSensorId, clearSchedulerBuffer); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index f055d559cc83..1a63dded4298 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -147,13 +147,13 @@ public class FaceService extends SystemService implements BiometricServiceCallba } @Override - public byte[] dumpSensorServiceStateProto(int sensorId) { + public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); final ProtoOutputStream proto = new ProtoOutputStream(); final ServiceProvider provider = getProviderForSensor(sensorId); if (provider != null) { - provider.dumpProtoState(sensorId, proto); + provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer); } proto.flush(); return proto.getBytes(); @@ -405,7 +405,7 @@ public class FaceService extends SystemService implements BiometricServiceCallba final ProtoOutputStream proto = new ProtoOutputStream(fd); for (ServiceProvider provider : mServiceProviders) { for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) { - provider.dumpProtoState(props.sensorId, proto); + provider.dumpProtoState(props.sensorId, proto, false); } } proto.flush(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 51b427d772a1..32428ac13114 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -121,7 +121,8 @@ public interface ServiceProvider { void scheduleInternalCleanup(int sensorId, int userId); - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto); + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer); void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index 13bbb7e8a704..211d79c6a263 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.BaseFrame; import android.hardware.face.Face; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; @@ -180,12 +182,21 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed(); } + // TODO(b/174619156): replace with notifyAuthenticationFrame and notifyEnrollmentFrame. @Override - public void notifyAcquired(int userId, int acquireInfo) { + public void notifyAcquired(int userId, int acquireInfo) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mSensor.getSessionForUser(userId).mHalSessionCallback - .onAcquired((byte) acquireInfo, 0 /* vendorCode */); + BaseFrame data = new BaseFrame(); + data.acquiredInfo = (byte) acquireInfo; + + AuthenticationFrame authenticationFrame = new AuthenticationFrame(); + authenticationFrame.data = data; + + // TODO(b/174619156): Currently onAuthenticationFrame and onEnrollmentFrame are the same. + // This will need to call the correct callback once the onAcquired callback is removed. + mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame( + authenticationFrame); } @Override 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 f09df1e1812e..632cc4bb177a 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 @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.face.Face; @@ -114,7 +115,8 @@ public class FaceEnrollClient extends EnrollClient<ISession> { try { // TODO(b/172593978): Pass features. - mCancellationSignal = getFreshDaemon().enroll(mSequentialId, + // TODO(b/174619156): Handle accessibility enrollment. + mCancellationSignal = getFreshDaemon().enroll(mSequentialId, EnrollmentType.DEFAULT, HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken), mPreviewSurface); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java index 5fb194c75d77..773647b4bf20 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.face.ISession; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.Map; @@ -65,4 +66,9 @@ class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> { mAuthenticatorIds.put(getTargetUserId(), authenticatorId); mCallback.onClientFinished(this, true /* success */); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_GET_AUTHENTICATOR_ID; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 810489b1c740..20318e3f836a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -571,9 +571,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer) { if (mSensors.contains(sensorId)) { - mSensors.get(sensorId).dumpProtoState(sensorId, proto); + mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index f355158d5727..71bac577a4a0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -25,6 +25,7 @@ import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutCache; @@ -81,4 +82,9 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> { mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId()); mCallback.onClientFinished(this, true /* success */); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_RESET_LOCKOUT; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 82ad387ab9f2..9b00ba63d846 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -22,6 +22,8 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; @@ -167,7 +169,8 @@ public class Sensor implements IBinder.DeathRecipient { } @Override - public void onAcquired(byte info, int vendorCode) { + public void onAuthenticationFrame(AuthenticationFrame frame) { + // TODO(b/174619156): propagate the frame to an AuthenticationClient mHandler.post(() -> { final BaseClientMonitor client = mScheduler.getCurrentClient(); if (!(client instanceof AcquisitionClient)) { @@ -177,7 +180,23 @@ public class Sensor implements IBinder.DeathRecipient { } final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(info, vendorCode); + acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode); + }); + } + + @Override + public void onEnrollmentFrame(EnrollmentFrame frame) { + // TODO(b/174619156): propagate the frame to an EnrollmentClient + mHandler.post(() -> { + final BaseClientMonitor client = mScheduler.getCurrentClient(); + if (!(client instanceof AcquisitionClient)) { + Slog.e(mTag, "onAcquired for non-acquisition client: " + + Utils.getClientName(client)); + return; + } + + final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; + acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode); }); } @@ -467,12 +486,13 @@ public class Sensor implements IBinder.DeathRecipient { mTestHalEnabled = enabled; } - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer) { final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES); proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE); - proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer)); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index 34bf9bc63a64..dc1efa057e3b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -49,8 +49,8 @@ public class TestHal extends IFace.Stub { } @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, - NativeHandle previewSurface) { + public ICancellationSignal enroll(int cookie, byte enrollmentType, + HardwareAuthToken hat, NativeHandle previewSurface) { return null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java index 50483d93f92b..189c726c3171 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java @@ -50,7 +50,7 @@ public class TestSession extends ISession.Stub { } @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, + public ICancellationSignal enroll(int cookie, byte enrollmentType, HardwareAuthToken hat, NativeHandle previewSurface) { return null; } 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 5e7ddeb64fb4..7010d9669b8d 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 @@ -767,12 +767,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public void dumpProtoState(int sensorId, ProtoOutputStream proto) { + public void dumpProtoState(int sensorId, ProtoOutputStream proto, + boolean clearSchedulerBuffer) { final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES); proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE); - proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer)); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java index 442303b037fb..722a3b843e12 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -88,4 +89,9 @@ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> { boolean getValue() { return mValue; } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_GET_FEATURE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java index e0548e073a98..14a46481ddc6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.ArrayList; @@ -71,4 +72,9 @@ public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> { mCallback.onClientFinished(this, false /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_RESET_LOCKOUT; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java index 43560434f147..6290e001f65b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -88,4 +89,9 @@ public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> { mCallback.onClientFinished(this, false /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_SET_FEATURE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java index 0e72f941b4d2..70e20339ce98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java @@ -24,6 +24,7 @@ import android.os.Environment; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; @@ -91,4 +92,9 @@ public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace mCallback.onClientFinished(this, false /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_UPDATE_ACTIVE_USER; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java index 312a3ba80d69..34a909908b5a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java @@ -53,8 +53,8 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub } @Override - public byte[] dumpSensorServiceStateProto() throws RemoteException { - return mFingerprintService.dumpSensorServiceStateProto(mSensorId); + public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException { + return mFingerprintService.dumpSensorServiceStateProto(mSensorId, clearSchedulerBuffer); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index d541eb3bd176..0265cb93ac8b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -116,13 +116,13 @@ public class FingerprintService extends SystemService implements BiometricServic } @Override - public byte[] dumpSensorServiceStateProto(int sensorId) { + public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); final ProtoOutputStream proto = new ProtoOutputStream(); final ServiceProvider provider = getProviderForSensor(sensorId); if (provider != null) { - provider.dumpProtoState(sensorId, proto); + provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer); } proto.flush(); return proto.getBytes(); @@ -419,7 +419,7 @@ public class FingerprintService extends SystemService implements BiometricServic for (ServiceProvider provider : mServiceProviders) { for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) { - provider.dumpProtoState(props.sensorId, proto); + provider.dumpProtoState(props.sensorId, proto, false); } } proto.flush(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 272e2b277941..d3b5b5af3282 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -128,7 +128,8 @@ public interface ServiceProvider { void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto); + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer); void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index e95447b49872..c2a30be6e2cb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -96,6 +96,16 @@ class BiometricTestSessionImpl extends ITestSession.Stub { public void onChallengeGenerated(int sensorId, long challenge) { } + + @Override + public void onUdfpsPointerDown(int sensorId) { + + } + + @Override + public void onUdfpsPointerUp(int sensorId) { + + } }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index c413c8b6362e..bd57fea25453 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -117,6 +117,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp public void onPointerDown(int x, int y, float minor, float major) { try { getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major); + if (getListener() != null) { + getListener().onUdfpsPointerDown(getSensorId(), getCookie()); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -126,6 +129,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp public void onPointerUp() { try { getFreshDaemon().onPointerUp(0 /* pointerId */); + if (getListener() != null) { + getListener().onUdfpsPointerUp(getSensorId(), getCookie()); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 3b376fee56d8..b4bb4f87a379 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -91,4 +92,9 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> { mCallback.onClientFinished(this, false /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_DETECT_INTERACTION; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java index 02d4ac3dd98e..ce1a31899a0c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.fingerprint.ISession; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.Map; @@ -65,4 +66,9 @@ class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> { mAuthenticatorIds.put(getTargetUserId(), authenticatorId); mCallback.onClientFinished(this, true /* success */); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_GET_AUTHENTICATOR_ID; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 8a666f9acb7f..727184f183c1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -627,9 +627,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer) { if (mSensors.contains(sensorId)) { - mSensors.get(sensorId).dumpProtoState(sensorId, proto); + mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index cd84cdfda5e5..ddcfcad59203 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -25,6 +25,7 @@ import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutCache; @@ -76,4 +77,9 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> { mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId()); mCallback.onClientFinished(this, true /* success */); } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_RESET_LOCKOUT; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 911f6b48af41..f0e7e1cf5d25 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -481,12 +481,13 @@ class Sensor implements IBinder.DeathRecipient { mTestHalEnabled = enabled; } - void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer) { final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES); proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT); - proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer)); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 95c4cee7e59e..6893e72486bc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -97,6 +97,16 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void onChallengeGenerated(int sensorId, long challenge) { } + + @Override + public void onUdfpsPointerDown(int sensorId) { + + } + + @Override + public void onUdfpsPointerUp(int sensorId) { + + } }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 6cc8687b3426..acc575fb1973 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -714,12 +714,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, + boolean clearSchedulerBuffer) { final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES); proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT); - proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer)); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 13e2e4fd7a37..589db6c82488 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -150,11 +150,25 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @Override public void onPointerDown(int x, int y, float minor, float major) { UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major); + if (getListener() != null) { + try { + getListener().onUdfpsPointerDown(getSensorId(), getCookie()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } } @Override public void onPointerUp() { UdfpsHelper.onFingerUp(getFreshDaemon()); + if (getListener() != null) { + try { + getListener().onUdfpsPointerUp(getSensorId(), getCookie()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } } public boolean isKeyguard() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 55995ea5b89d..6318139f3aae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -28,6 +28,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -123,4 +124,9 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> Slog.e(TAG, "Remote exception when sending onDetected", e); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_DETECT_INTERACTION; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index f6ec4d943543..11ffbb27128a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.util.Slog; +import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; @@ -121,4 +122,9 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr mCallback.onClientFinished(this, false /* success */); } } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_UPDATE_ACTIVE_USER; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java index 3e5b88cca1e4..8e84613b2d64 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java @@ -50,7 +50,7 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub { } @Override - public byte[] dumpSensorServiceStateProto() throws RemoteException { + public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException { return null; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index ba6cbcd3c796..ab0360b0395a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -36,13 +36,17 @@ import android.net.NetworkInfo; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosFilterParcelable; +import android.net.QosSession; import android.net.TcpKeepalivePacketData; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.SystemClock; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -53,7 +57,6 @@ import com.android.internal.util.WakeupMessage; import com.android.server.ConnectivityService; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; @@ -323,18 +326,20 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final ConnectivityService mConnService; private final Context mContext; private final Handler mHandler; + private final QosCallbackTracker mQosCallbackTracker; public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, - int creatorUid) { + int creatorUid, QosCallbackTracker qosCallbackTracker) { Objects.requireNonNull(net); Objects.requireNonNull(info); Objects.requireNonNull(lp); Objects.requireNonNull(nc); Objects.requireNonNull(context); Objects.requireNonNull(config); + Objects.requireNonNull(qosCallbackTracker); networkAgent = na; network = net; networkInfo = info; @@ -348,6 +353,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { networkAgentConfig = config; this.factorySerialNumber = factorySerialNumber; this.creatorUid = creatorUid; + mQosCallbackTracker = qosCallbackTracker; } private class AgentDeathMonitor implements IBinder.DeathRecipient { @@ -533,6 +539,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } } + /** + * Notify the NetworkAgent that the qos filter should be registered against the given qos + * callback id. + */ + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilter qosFilter) { + try { + networkAgent.onQosFilterCallbackRegistered(qosCallbackId, + new QosFilterParcelable(qosFilter)); + } catch (final RemoteException e) { + Log.e(TAG, "Error registering a qos callback id against a qos filter", e); + } + } + + /** + * Notify the NetworkAgent that the given qos callback id should be unregistered. + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + try { + networkAgent.onQosCallbackUnregistered(qosCallbackId); + } catch (RemoteException e) { + Log.e(TAG, "Error unregistering a qos callback id", e); + } + } + // TODO: consider moving out of NetworkAgentInfo into its own class private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub { private final Handler mHandler; @@ -583,16 +614,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { @Override public void sendUnderlyingNetworks(@Nullable List<Network> networks) { - final Bundle args = new Bundle(); - if (networks instanceof ArrayList<?>) { - args.putParcelableArrayList(NetworkAgent.UNDERLYING_NETWORKS_KEY, - (ArrayList<Network>) networks); - } else { - args.putParcelableArrayList(NetworkAgent.UNDERLYING_NETWORKS_KEY, - networks == null ? null : new ArrayList<>(networks)); - } mHandler.obtainMessage(NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED, - new Pair<>(NetworkAgentInfo.this, args)).sendToTarget(); + new Pair<>(NetworkAgentInfo.this, networks)).sendToTarget(); + } + + @Override + public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes); + } + + @Override + public void sendQosSessionLost(final int qosCallbackId, final QosSession session) { + mQosCallbackTracker.sendEventQosSessionLost(qosCallbackId, session); + } + + @Override + public void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java new file mode 100644 index 000000000000..816bf2be0d69 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE; + +import android.annotation.NonNull; +import android.net.IQosCallback; +import android.net.Network; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Slog; + +import java.util.Objects; + +/** + * Wraps callback related information and sends messages between network agent and the application. + * <p/> + * This is a satellite class of {@link com.android.server.ConnectivityService} and not meant + * to be used in other contexts. + * + * @hide + */ +class QosCallbackAgentConnection implements IBinder.DeathRecipient { + private static final String TAG = QosCallbackAgentConnection.class.getSimpleName(); + private static final boolean DBG = false; + + private final int mAgentCallbackId; + @NonNull private final QosCallbackTracker mQosCallbackTracker; + @NonNull private final IQosCallback mCallback; + @NonNull private final IBinder mBinder; + @NonNull private final QosFilter mFilter; + @NonNull private final NetworkAgentInfo mNetworkAgentInfo; + + private final int mUid; + + /** + * Gets the uid + * @return uid + */ + int getUid() { + return mUid; + } + + /** + * Gets the binder + * @return binder + */ + @NonNull + IBinder getBinder() { + return mBinder; + } + + /** + * Gets the callback id + * + * @return callback id + */ + int getAgentCallbackId() { + return mAgentCallbackId; + } + + /** + * Gets the network tied to the callback of this connection + * + * @return network + */ + @NonNull + Network getNetwork() { + return mFilter.getNetwork(); + } + + QosCallbackAgentConnection(@NonNull final QosCallbackTracker qosCallbackTracker, + final int agentCallbackId, + @NonNull final IQosCallback callback, + @NonNull final QosFilter filter, + final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + Objects.requireNonNull(qosCallbackTracker, "qosCallbackTracker must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(filter, "filter must be non-null"); + Objects.requireNonNull(networkAgentInfo, "networkAgentInfo must be non-null"); + + mQosCallbackTracker = qosCallbackTracker; + mAgentCallbackId = agentCallbackId; + mCallback = callback; + mFilter = filter; + mUid = uid; + mBinder = mCallback.asBinder(); + mNetworkAgentInfo = networkAgentInfo; + } + + @Override + public void binderDied() { + logw("binderDied: binder died with callback id: " + mAgentCallbackId); + mQosCallbackTracker.unregisterCallback(mCallback); + } + + void unlinkToDeathRecipient() { + mBinder.unlinkToDeath(this, 0); + } + + // Returns false if the NetworkAgent was never notified. + boolean sendCmdRegisterCallback() { + final int exceptionType = mFilter.validate(); + if (exceptionType != EX_TYPE_FILTER_NONE) { + try { + if (DBG) log("sendCmdRegisterCallback: filter validation failed"); + mCallback.onError(exceptionType); + } catch (final RemoteException e) { + loge("sendCmdRegisterCallback:", e); + } + return false; + } + + try { + mBinder.linkToDeath(this, 0); + } catch (final RemoteException e) { + loge("failed linking to death recipient", e); + return false; + } + mNetworkAgentInfo.onQosFilterCallbackRegistered(mAgentCallbackId, mFilter); + return true; + } + + void sendCmdUnregisterCallback() { + if (DBG) log("sendCmdUnregisterCallback: unregistering"); + mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId); + } + + void sendEventQosSessionAvailable(final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + try { + if (DBG) log("sendEventQosSessionAvailable: sending..."); + mCallback.onQosEpsBearerSessionAvailable(session, attributes); + } catch (final RemoteException e) { + loge("sendEventQosSessionAvailable: remote exception", e); + } + } + + void sendEventQosSessionLost(@NonNull final QosSession session) { + try { + if (DBG) log("sendEventQosSessionLost: sending..."); + mCallback.onQosSessionLost(session); + } catch (final RemoteException e) { + loge("sendEventQosSessionLost: remote exception", e); + } + } + + void sendEventQosCallbackError(@QosCallbackException.ExceptionType final int exceptionType) { + try { + if (DBG) log("sendEventQosCallbackError: sending..."); + mCallback.onError(exceptionType); + } catch (final RemoteException e) { + loge("sendEventQosCallbackError: remote exception", e); + } + } + + private static void log(@NonNull final String msg) { + Slog.d(TAG, msg); + } + + private static void logw(@NonNull final String msg) { + Slog.w(TAG, msg); + } + + private static void loge(@NonNull final String msg, final Throwable t) { + Slog.e(TAG, msg, t); + } + + private static void logwtf(@NonNull final String msg) { + Slog.wtf(TAG, msg); + } +} diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java new file mode 100644 index 000000000000..87b4c162a2cc --- /dev/null +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IQosCallback; +import android.net.Network; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Slog; + +import com.android.internal.util.CollectionUtils; +import com.android.server.ConnectivityService; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks qos callbacks and handles the communication between the network agent and application. + * <p/> + * Any method prefixed by handle must be called from the + * {@link com.android.server.ConnectivityService} handler thread. + * + * @hide + */ +public class QosCallbackTracker { + private static final String TAG = QosCallbackTracker.class.getSimpleName(); + private static final boolean DBG = true; + + @NonNull + private final Handler mConnectivityServiceHandler; + + @NonNull + private final ConnectivityService.PerUidCounter mNetworkRequestCounter; + + /** + * Each agent gets a unique callback id that is used to proxy messages back to the original + * callback. + * <p/> + * Note: The fact that this is initialized to 0 is to ensure that the thread running + * {@link #handleRegisterCallback(IQosCallback, QosFilter, int, NetworkAgentInfo)} sees the + * initialized value. This would not necessarily be the case if the value was initialized to + * the non-default value. + * <p/> + * Note: The term previous does not apply to the first callback id that is assigned. + */ + private int mPreviousAgentCallbackId = 0; + + @NonNull + private final List<QosCallbackAgentConnection> mConnections = new ArrayList<>(); + + /** + * + * @param connectivityServiceHandler must be the same handler used with + * {@link com.android.server.ConnectivityService} + * @param networkRequestCounter keeps track of the number of open requests under a given + * uid + */ + public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler, + final ConnectivityService.PerUidCounter networkRequestCounter) { + mConnectivityServiceHandler = connectivityServiceHandler; + mNetworkRequestCounter = networkRequestCounter; + } + + /** + * Registers the callback with the tracker + * + * @param callback the callback to register + * @param filter the filter being registered alongside the callback + */ + public void registerCallback(@NonNull final IQosCallback callback, + @NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo) { + final int uid = Binder.getCallingUid(); + + // Enforce that the number of requests under this uid has exceeded the allowed number + mNetworkRequestCounter.incrementCountOrThrow(uid); + + mConnectivityServiceHandler.post( + () -> handleRegisterCallback(callback, filter, uid, networkAgentInfo)); + } + + private void handleRegisterCallback(@NonNull final IQosCallback callback, + @NonNull final QosFilter filter, final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + final QosCallbackAgentConnection ac = + handleRegisterCallbackInternal(callback, filter, uid, networkAgentInfo); + if (ac != null) { + if (DBG) log("handleRegisterCallback: added callback " + ac.getAgentCallbackId()); + mConnections.add(ac); + } else { + mNetworkRequestCounter.decrementCount(uid); + } + } + + private QosCallbackAgentConnection handleRegisterCallbackInternal( + @NonNull final IQosCallback callback, + @NonNull final QosFilter filter, final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + final IBinder binder = callback.asBinder(); + if (CollectionUtils.any(mConnections, c -> c.getBinder().equals(binder))) { + // A duplicate registration would have only made this far due to a programming error. + logwtf("handleRegisterCallback: Callbacks can only be register once."); + return null; + } + + mPreviousAgentCallbackId = mPreviousAgentCallbackId + 1; + final int newCallbackId = mPreviousAgentCallbackId; + + final QosCallbackAgentConnection ac = + new QosCallbackAgentConnection(this, newCallbackId, callback, + filter, uid, networkAgentInfo); + + final int exceptionType = filter.validate(); + if (exceptionType != QosCallbackException.EX_TYPE_FILTER_NONE) { + ac.sendEventQosCallbackError(exceptionType); + return null; + } + + // Only add to the callback maps if the NetworkAgent successfully registered it + if (!ac.sendCmdRegisterCallback()) { + // There was an issue when registering the agent + if (DBG) log("handleRegisterCallback: error sending register callback"); + mNetworkRequestCounter.decrementCount(uid); + return null; + } + return ac; + } + + /** + * Unregisters callback + * @param callback callback to unregister + */ + public void unregisterCallback(@NonNull final IQosCallback callback) { + mConnectivityServiceHandler.post(() -> handleUnregisterCallback(callback.asBinder(), true)); + } + + private void handleUnregisterCallback(@NonNull final IBinder binder, + final boolean sendToNetworkAgent) { + final QosCallbackAgentConnection agentConnection = + CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder)); + if (agentConnection == null) { + logw("handleUnregisterCallback: agentConnection is null"); + return; + } + + if (DBG) { + log("handleUnregisterCallback: unregister " + + agentConnection.getAgentCallbackId()); + } + + mNetworkRequestCounter.decrementCount(agentConnection.getUid()); + mConnections.remove(agentConnection); + + if (sendToNetworkAgent) { + agentConnection.sendCmdUnregisterCallback(); + } + agentConnection.unlinkToDeathRecipient(); + } + + /** + * Called when the NetworkAgent sends the qos session available event + * + * @param qosCallbackId the callback id that the qos session is now available to + * @param session the qos session that is now available + * @param attributes the qos attributes that are now available on the qos session + */ + public void sendEventQosSessionAvailable(final int qosCallbackId, + final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ", + ac -> ac.sendEventQosSessionAvailable(session, attributes)); + } + + /** + * Called when the NetworkAgent sends the qos session lost event + * + * @param qosCallbackId the callback id that lost the qos session + * @param session the corresponding qos session + */ + public void sendEventQosSessionLost(final int qosCallbackId, + final QosSession session) { + runOnAgentConnection(qosCallbackId, "sendEventQosSessionLost: ", + ac -> ac.sendEventQosSessionLost(session)); + } + + /** + * Called when the NetworkAgent sends the qos session on error event + * + * @param qosCallbackId the callback id that should receive the exception + * @param exceptionType the type of exception that caused the callback to error + */ + public void sendEventQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + runOnAgentConnection(qosCallbackId, "sendEventQosCallbackError: ", + ac -> { + ac.sendEventQosCallbackError(exceptionType); + handleUnregisterCallback(ac.getBinder(), false); + }); + } + + /** + * Unregisters all callbacks associated to this network agent + * + * Note: Must be called on the connectivity service handler thread + * + * @param network the network that was released + */ + public void handleNetworkReleased(@Nullable final Network network) { + final List<QosCallbackAgentConnection> connections = + CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network)); + + for (final QosCallbackAgentConnection agentConnection : connections) { + agentConnection.sendEventQosCallbackError( + QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + + // Call unregister workflow w\o sending anything to agent since it is disconnected. + handleUnregisterCallback(agentConnection.getBinder(), false); + } + } + + private interface AgentConnectionAction { + void execute(@NonNull QosCallbackAgentConnection agentConnection); + } + + @Nullable + private void runOnAgentConnection(final int qosCallbackId, + @NonNull final String logPrefix, + @NonNull final AgentConnectionAction action) { + mConnectivityServiceHandler.post(() -> { + final QosCallbackAgentConnection ac = + CollectionUtils.find(mConnections, + c -> c.getAgentCallbackId() == qosCallbackId); + if (ac == null) { + loge(logPrefix + ": " + qosCallbackId + " missing callback id"); + return; + } + + action.execute(ac); + }); + } + + private static void log(final String msg) { + Slog.d(TAG, msg); + } + + private static void logw(final String msg) { + Slog.w(TAG, msg); + } + + private static void loge(final String msg) { + Slog.e(TAG, msg); + } + + private static void logwtf(final String msg) { + Slog.wtf(TAG, msg); + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 68708d36f259..d6872217eab6 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -24,6 +24,7 @@ import android.util.Slog; import android.view.DisplayAddress; import com.android.internal.BrightnessSynchronizer; +import com.android.internal.R; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; @@ -64,6 +65,7 @@ public class DisplayDeviceConfig { private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d"; private static final String NO_SUFFIX_FORMAT = "%d"; private static final long STABLE_FLAG = 1L << 62; + // Float.NaN (used as invalid for brightness) cannot be stored in config.xml // so -2 is used instead private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f; @@ -75,6 +77,10 @@ public class DisplayDeviceConfig { private float mBrightnessMinimum = Float.NaN; private float mBrightnessMaximum = 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 List<String> mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; @@ -177,6 +183,22 @@ public class DisplayDeviceConfig { return mBrightnessDefault; } + public float getBrightnessRampFastDecrease() { + return mBrightnessRampFastDecrease; + } + + public float getBrightnessRampFastIncrease() { + return mBrightnessRampFastIncrease; + } + + public float getBrightnessRampSlowDecrease() { + return mBrightnessRampSlowDecrease; + } + + public float getBrightnessRampSlowIncrease() { + return mBrightnessRampSlowIncrease; + } + /** * @param quirkValue The quirk to test. * @return {@code true} if the specified quirk is present in this configuration, @@ -210,6 +232,10 @@ public class DisplayDeviceConfig { + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + "}"; return str; } @@ -265,6 +291,7 @@ public class DisplayDeviceConfig { loadBrightnessConstraintsFromConfigXml(); loadHighBrightnessModeData(config); loadQuirks(config); + loadBrightnessRamps(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -278,6 +305,7 @@ public class DisplayDeviceConfig { // If no ddc exists, use config.xml loadBrightnessDefaultFromConfigXml(); loadBrightnessConstraintsFromConfigXml(); + loadBrightnessRampsFromConfigXml(); } private void initFromPmValues() { @@ -397,6 +425,41 @@ public class DisplayDeviceConfig { } } + private void loadBrightnessRamps(DisplayConfiguration config) { + // Priority 1: Value in the display device config (float) + // Priority 2: Value in the config.xml (int) + final BigDecimal fastDownDecimal = config.getScreenBrightnessRampFastDecrease(); + final BigDecimal fastUpDecimal = config.getScreenBrightnessRampFastIncrease(); + final BigDecimal slowDownDecimal = config.getScreenBrightnessRampSlowDecrease(); + final BigDecimal slowUpDecimal = config.getScreenBrightnessRampSlowIncrease(); + + if (fastDownDecimal != null && fastUpDecimal != null && slowDownDecimal != null + && slowUpDecimal != null) { + mBrightnessRampFastDecrease = fastDownDecimal.floatValue(); + mBrightnessRampFastIncrease = fastUpDecimal.floatValue(); + mBrightnessRampSlowDecrease = slowDownDecimal.floatValue(); + mBrightnessRampSlowIncrease = slowUpDecimal.floatValue(); + } else { + if (fastDownDecimal != null || fastUpDecimal != null || slowDownDecimal != null + || slowUpDecimal != null) { + Slog.w(TAG, "Per display brightness ramp values ignored because not all " + + "values are present in display device config"); + } + loadBrightnessRampsFromConfigXml(); + } + } + + private void loadBrightnessRampsFromConfigXml() { + mBrightnessRampFastIncrease = BrightnessSynchronizer.brightnessIntToFloat( + mContext.getResources().getInteger(R.integer.config_brightness_ramp_rate_fast)); + mBrightnessRampSlowIncrease = BrightnessSynchronizer.brightnessIntToFloat( + mContext.getResources().getInteger(R.integer.config_brightness_ramp_rate_slow)); + // config.xml uses the same values for both increasing and decreasing brightness + // transitions so we assign them to the same values here. + mBrightnessRampFastDecrease = mBrightnessRampFastIncrease; + mBrightnessRampSlowDecrease = mBrightnessRampSlowIncrease; + } + /** * Container for high brightness mode configuration data. */ diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java index 2ba875813734..1b5065ad89e4 100644 --- a/services/core/java/com/android/server/display/DisplayGroup.java +++ b/services/core/java/com/android/server/display/DisplayGroup.java @@ -21,7 +21,8 @@ import java.util.List; /** * Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and - * operations. + * operations; particularly display-state. + * * @hide */ public class DisplayGroup { @@ -35,17 +36,44 @@ public class DisplayGroup { mGroupId = groupId; } + /** Returns the identifier for the Group. */ int getGroupId() { return mGroupId; } - void addDisplay(LogicalDisplay display) { + /** + * Adds the provided {@code display} to the Group + * + * @param display the {@link LogicalDisplay} to add to the Group + */ + void addDisplayLocked(LogicalDisplay display) { if (!mDisplays.contains(display)) { mDisplays.add(display); } } - boolean removeDisplay(LogicalDisplay display) { + /** + * Removes the provided {@code display} from the Group. + * + * @param display The {@link LogicalDisplay} to remove from the Group. + * @return {@code true} if the {@code display} was removed; otherwise {@code false} + */ + boolean removeDisplayLocked(LogicalDisplay display) { return mDisplays.remove(display); } + + /** Returns {@code true} if there are no {@link LogicalDisplay LogicalDisplays} in the Group. */ + boolean isEmptyLocked() { + return mDisplays.isEmpty(); + } + + /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */ + int getSizeLocked() { + return mDisplays.size(); + } + + /** Returns the ID of the {@link LogicalDisplay} at the provided {@code index}. */ + int getIdLocked(int index) { + return mDisplays.get(index).getDisplayIdLocked(); + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 55103ca6cd1c..bb4c9dda2195 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -57,6 +57,7 @@ import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayGroupListener; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; @@ -190,6 +191,7 @@ public final class DisplayManagerService extends SystemService { private static final int MSG_UPDATE_VIEWPORT = 5; private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6; private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7; + private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8; private final Context mContext; private final DisplayManagerHandler mHandler; @@ -237,36 +239,54 @@ public final class DisplayManagerService extends SystemService { private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners = new CopyOnWriteArrayList<DisplayTransactionListener>(); + /** List of all display group listeners. */ + private final CopyOnWriteArrayList<DisplayGroupListener> mDisplayGroupListeners = + new CopyOnWriteArrayList<>(); + /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */ private final SparseArray<DisplayPowerController> mDisplayPowerControllers = new SparseArray<>(); /** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */ private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() { + // Synchronized to avoid race conditions when updating multiple display states. @Override - public void requestDisplayState(int displayId, int state, float brightness) { - // TODO (b/168210494): Stop applying default display state to all displays. - if (displayId != Display.DEFAULT_DISPLAY) { - return; - } - final int[] displayIds; + public synchronized void requestDisplayState(int displayId, int state, float brightness) { + boolean allInactive = true; + boolean allOff = true; + final boolean stateChanged; synchronized (mSyncRoot) { - displayIds = mLogicalDisplayMapper.getDisplayIdsLocked(); + final int index = mDisplayStates.indexOfKey(displayId); + final int newState = mDisplayStates.valueAt(index); + stateChanged = index == -1 || state != newState; + if (stateChanged) { + final int size = mDisplayStates.size(); + for (int i = 0; i < size; i++) { + final int displayState = i == index ? newState : state; + if (displayState != Display.STATE_OFF) { + allOff = false; + } + if (Display.isActiveState(displayState)) { + allInactive = false; + } + if (!allOff && !allInactive) { + break; + } + } + } } // The order of operations is important for legacy reasons. if (state == Display.STATE_OFF) { - for (int id : displayIds) { - requestDisplayStateInternal(id, state, brightness); - } + requestDisplayStateInternal(displayId, state, brightness); } - mDisplayPowerCallbacks.onDisplayStateChange(state); + if (stateChanged) { + mDisplayPowerCallbacks.onDisplayStateChange(allInactive, allOff); + } if (state != Display.STATE_OFF) { - for (int id : displayIds) { - requestDisplayStateInternal(id, state, brightness); - } + requestDisplayStateInternal(displayId, state, brightness); } } }; @@ -1152,7 +1172,7 @@ public final class DisplayManagerService extends SystemService { private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); - mDisplayPowerControllers.delete(displayId); + mDisplayPowerControllers.removeReturnOld(displayId).stop(); mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); @@ -1669,6 +1689,11 @@ public final class DisplayManagerService extends SystemService { mHandler.sendMessage(msg); } + private void sendDisplayGroupEvent(int groupId, int event) { + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event); + mHandler.sendMessage(msg); + } + private void sendDisplayEventFrameRateOverrideLocked(int displayId) { Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE, displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); @@ -1713,6 +1738,35 @@ public final class DisplayManagerService extends SystemService { mTempCallbacks.clear(); } + // Runs on Handler thread. + // Delivers display group event notifications to callbacks. + private void deliverDisplayGroupEvent(int groupId, int event) { + if (DEBUG) { + Slog.d(TAG, "Delivering display group event: groupId=" + groupId + ", event=" + + event); + } + + switch (event) { + case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED: + for (DisplayGroupListener listener : mDisplayGroupListeners) { + listener.onDisplayGroupAdded(groupId); + } + break; + + case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED: + for (DisplayGroupListener listener : mDisplayGroupListeners) { + listener.onDisplayGroupChanged(groupId); + } + break; + + case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED: + for (DisplayGroupListener listener : mDisplayGroupListeners) { + listener.onDisplayGroupRemoved(groupId); + } + break; + } + } + private IMediaProjectionManager getProjectionService() { if (mProjectionService == null) { IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE); @@ -1935,6 +1989,11 @@ public final class DisplayManagerService extends SystemService { } deliverDisplayEvent(msg.arg1, uids, msg.arg2); break; + + case MSG_DELIVER_DISPLAY_GROUP_EVENT: + deliverDisplayGroupEvent(msg.arg1, msg.arg2); + break; + } } } @@ -1966,6 +2025,11 @@ public final class DisplayManagerService extends SystemService { } @Override + public void onDisplayGroupEventLocked(int groupId, int event) { + sendDisplayGroupEvent(groupId, event); + } + + @Override public void onTraversalRequested() { synchronized (mSyncRoot) { scheduleTraversalLocked(false); @@ -2700,11 +2764,25 @@ public final class DisplayManagerService extends SystemService { } @Override - public boolean requestPowerState(DisplayPowerRequest request, + public boolean requestPowerState(int groupId, DisplayPowerRequest request, boolean waitForNegativeProximity) { synchronized (mSyncRoot) { - return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY) - .requestPowerState(request, waitForNegativeProximity); + final DisplayGroup displayGroup = mLogicalDisplayMapper.getDisplayGroupLocked( + groupId); + if (displayGroup == null) { + return true; + } + + final int size = displayGroup.getSizeLocked(); + boolean ready = true; + for (int i = 0; i < size; i++) { + final DisplayPowerController displayPowerController = + mDisplayPowerControllers.get(displayGroup.getIdLocked(i)); + ready &= displayPowerController.requestPowerState(request, + waitForNegativeProximity); + } + + return ready; } } @@ -2717,10 +2795,13 @@ public final class DisplayManagerService extends SystemService { } @Override - public int getDisplayGroupId(int displayId) { - synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getDisplayGroupIdLocked(displayId); - } + public void registerDisplayGroupListener(DisplayGroupListener listener) { + mDisplayGroupListeners.add(listener); + } + + @Override + public void unregisterDisplayGroupListener(DisplayGroupListener listener) { + mDisplayGroupListeners.remove(listener); } @Override diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 811625b06cf0..cb73c09acfbd 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -331,8 +331,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); // Brightness animation ramp rates in brightness units per second - private final float mBrightnessRampRateSlow; - private final float mBrightnessRampRateFast; + private float mBrightnessRampRateFastDecrease; + private float mBrightnessRampRateFastIncrease; + private float mBrightnessRampRateSlowDecrease; + private float mBrightnessRampRateSlowIncrease; // Whether or not to skip the initial brightness ramps into STATE_ON. @@ -459,10 +461,21 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing); - mBrightnessRampRateFast = BrightnessSynchronizer.brightnessIntToFloat(resources.getInteger( - com.android.internal.R.integer.config_brightness_ramp_rate_fast)); - mBrightnessRampRateSlow = BrightnessSynchronizer.brightnessIntToFloat(resources.getInteger( - com.android.internal.R.integer.config_brightness_ramp_rate_slow)); + + 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; + } mSkipScreenOnBrightnessRamp = resources.getBoolean( com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); @@ -683,6 +696,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // TODO: b/175821789 - Support high brightness on multiple (folding) displays } + /** + * Unregisters all listeners and interrupts all running threads; halting future work. + * + * This method should be called when the DisplayPowerController is no longer in use; i.e. when + * the {@link #mDisplayId display} has been removed. + */ + public void stop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + mPowerState.stop(); + } + private void sendUpdatePowerState() { synchronized (mLock) { sendUpdatePowerStateLocked(); @@ -772,7 +796,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call boolean mustInitialize = false; int brightnessAdjustmentFlags = 0; mBrightnessReasonTemp.set(null); - synchronized (mLock) { mPendingUpdatePowerStateLocked = false; if (mPendingRequestLocked == null) { @@ -1111,13 +1134,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // user even when the display is all black. float animateValue = brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT ? PowerManager.BRIGHTNESS_MIN : brightnessState; - if (isValidBrightnessValue(animateValue)) { + final float currentBrightness = mPowerState.getScreenBrightness(); + if (isValidBrightnessValue(animateValue) + && !BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)) { if (initialRampSkip || hasBrightnessBuckets || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { animateScreenBrightness(animateValue, SCREEN_ANIMATION_RATE_MINIMUM); } else { - animateScreenBrightness(animateValue, - slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); + boolean isIncreasing = animateValue > currentBrightness; + final float rampSpeed; + if (isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowIncrease; + } else if (isIncreasing && !slowChange) { + rampSpeed = mBrightnessRampRateFastIncrease; + } else if (!isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowDecrease; + } else { + rampSpeed = mBrightnessRampRateFastDecrease; + } + animateScreenBrightness(animateValue, rampSpeed); } } diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 54f30a954c33..51ba065f601c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -72,6 +72,8 @@ final class DisplayPowerState { private Runnable mCleanListener; + private volatile boolean mStopped; + public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) { mHandler = new Handler(true /*async*/); mChoreographer = Choreographer.getInstance(); @@ -263,6 +265,18 @@ final class DisplayPowerState { } } + /** + * Interrupts all running threads; halting future work. + * + * This method should be called when the DisplayPowerState is no longer in use; i.e. when + * the {@link #mDisplayId display} has been removed. + */ + public void stop() { + mHandler.removeCallbacksAndMessages(null); + mStopped = true; + mPhotonicModulator.interrupt(); + } + public void dump(PrintWriter pw) { pw.println(); pw.println("Display Power State:"); @@ -427,7 +441,11 @@ final class DisplayPowerState { if (!stateChanged && !backlightChanged) { try { mLock.wait(); - } catch (InterruptedException ex) { } + } catch (InterruptedException ex) { + if (mStopped) { + return; + } + } continue; } mActualState = state; diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index bb2fbed354aa..ecb837da458b 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -17,7 +17,6 @@ package com.android.server.display; import android.content.Context; -import android.os.Process; import android.os.SystemProperties; import android.text.TextUtils; import android.util.IndentingPrintWriter; @@ -55,6 +54,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4; public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5; + public static final int DISPLAY_GROUP_EVENT_ADDED = 1; + public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; + public static final int DISPLAY_GROUP_EVENT_REMOVED = 3; + /** * Temporary display info, used for comparing display configurations. */ @@ -99,7 +102,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1; /** A mapping from logical display id to display group. */ - private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>(); + private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>(); private final DisplayDeviceRepository mDisplayDeviceRepo; private final Listener mListener; @@ -154,10 +157,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return null; } - public int[] getDisplayIdsLocked() { - return getDisplayIdsLocked(Process.SYSTEM_UID); - } - public int[] getDisplayIdsLocked(int callingUid) { final int count = mLogicalDisplays.size(); int[] displayIds = new int[count]; @@ -182,13 +181,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } - public int getDisplayGroupIdLocked(int displayId) { - final DisplayGroup displayGroup = mDisplayGroups.get(displayId); - if (displayGroup != null) { - return displayGroup.getGroupId(); + public DisplayGroup getDisplayGroupLocked(int groupId) { + final int size = mDisplayIdToGroupMap.size(); + for (int i = 0; i < size; i++) { + final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i); + if (displayGroup.getGroupId() == groupId) { + return displayGroup; + } } - return -1; + return null; } public void dumpLocked(PrintWriter pw) { @@ -325,17 +327,31 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mLogicalDisplays.put(displayId, display); final DisplayGroup displayGroup; - if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) { + final boolean addNewDisplayGroup = + isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0; + if (addNewDisplayGroup) { final int groupId = assignDisplayGroupIdLocked(isDefault); displayGroup = new DisplayGroup(groupId); } else { - displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY); + displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY); + } + displayGroup.addDisplayLocked(display); + mDisplayIdToGroupMap.append(displayId, displayGroup); + + if (addNewDisplayGroup) { + // Group added events happen before Logical Display added events. + mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), + LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); } - displayGroup.addDisplay(display); - mDisplayGroups.append(displayId, displayGroup); mListener.onLogicalDisplayEventLocked(display, LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED); + + if (!addNewDisplayGroup) { + // Group changed events happen after Logical Display added events. + mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), + LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); + } } /** @@ -352,31 +368,45 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides = display.getFrameRateOverrides(); display.updateLocked(mDisplayDeviceRepo); + final DisplayGroup changedDisplayGroup; if (!display.isValidLocked()) { mLogicalDisplays.removeAt(i); - mDisplayGroups.removeReturnOld(displayId).removeDisplay(display); + final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId); + displayGroup.removeDisplayLocked(display); mListener.onLogicalDisplayEventLocked(display, LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED); + + changedDisplayGroup = displayGroup; } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { final int flags = display.getDisplayInfoLocked().flags; - final DisplayGroup defaultDisplayGroup = mDisplayGroups.get( + final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get( Display.DEFAULT_DISPLAY); if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) { // The display should have its own DisplayGroup. - if (defaultDisplayGroup.removeDisplay(display)) { + if (defaultDisplayGroup.removeDisplayLocked(display)) { final int groupId = assignDisplayGroupIdLocked(false); final DisplayGroup displayGroup = new DisplayGroup(groupId); - displayGroup.addDisplay(display); - mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup); + displayGroup.addDisplayLocked(display); + mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup); + mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), + LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED); + changedDisplayGroup = defaultDisplayGroup; + } else { + changedDisplayGroup = null; } } else { // The display should be a part of the default DisplayGroup. - final DisplayGroup displayGroup = mDisplayGroups.get(displayId); + final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId); if (displayGroup != defaultDisplayGroup) { - displayGroup.removeDisplay(display); - defaultDisplayGroup.addDisplay(display); - mDisplayGroups.put(displayId, defaultDisplayGroup); + displayGroup.removeDisplayLocked(display); + defaultDisplayGroup.addDisplayLocked(display); + mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), + LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); + mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup); + changedDisplayGroup = displayGroup; + } else { + changedDisplayGroup = null; } } @@ -388,6 +418,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { mListener.onLogicalDisplayEventLocked(display, LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); + changedDisplayGroup = null; } else { // While applications shouldn't know nor care about the non-overridden info, we // still need to let WindowManager know so it can update its own internal state for @@ -397,6 +428,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mListener.onLogicalDisplayEventLocked(display, LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED); } + changedDisplayGroup = null; + } + + // CHANGED and REMOVED DisplayGroup events should always fire after Display events. + if (changedDisplayGroup != null) { + final int event = changedDisplayGroup.isEmptyLocked() + ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED + : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED; + mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event); } } } @@ -432,6 +472,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public interface Listener { void onLogicalDisplayEventLocked(LogicalDisplay display, int event); + void onDisplayGroupEventLocked(int groupId, int event); void onTraversalRequested(); } } diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index a0d9e8e0a6fb..425e3ab0d471 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -31,6 +31,8 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.security.FileIntegrityService; +import com.android.server.security.VerityUtils; import java.io.File; import java.io.FileDescriptor; @@ -39,6 +41,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.NioUtils; import java.nio.channels.FileChannel; +import java.util.Arrays; import java.util.Map; /** A service for managing system fonts. */ @@ -78,7 +81,17 @@ public final class FontManagerService { private static class OtfFontFileParser implements UpdatableFontDir.FontFileParser { @Override - public long getVersion(File file) throws IOException { + public String getPostScriptName(File file) throws IOException { + ByteBuffer buffer = mmap(file); + try { + return FontFileUtil.getPostScriptName(buffer, 0); + } finally { + NioUtils.freeDirectBuffer(buffer); + } + } + + @Override + public long getRevision(File file) throws IOException { ByteBuffer buffer = mmap(file); try { return FontFileUtil.getRevision(buffer, 0); @@ -95,18 +108,48 @@ public final class FontManagerService { } } + private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil { + @Override + public boolean hasFsverity(String filePath) { + return VerityUtils.hasFsverity(filePath); + } + + @Override + public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException { + VerityUtils.setUpFsverity(filePath, pkcs7Signature); + } + + @Override + public boolean rename(File src, File dest) { + // rename system call preserves fs-verity bit. + return src.renameTo(dest); + } + } + @Nullable private final UpdatableFontDir mUpdatableFontDir; @GuardedBy("FontManagerService.this") - @Nullable SystemFontSettings mCurrentFontSettings = null; + @Nullable + private SystemFontSettings mCurrentFontSettings = null; private FontManagerService() { - mUpdatableFontDir = ENABLE_FONT_UPDATES - ? new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser()) : null; + mUpdatableFontDir = createUpdatableFontDir(); } - @NonNull private SystemFontSettings getCurrentFontSettings() { + @Nullable + private static UpdatableFontDir createUpdatableFontDir() { + if (!ENABLE_FONT_UPDATES) return null; + // If apk verity is supported, fs-verity should be available. + if (!FileIntegrityService.isApkVeritySupported()) return null; + return new UpdatableFontDir(new File(FONT_FILES_DIR), + Arrays.asList(new File(SystemFonts.SYSTEM_FONT_DIR), + new File(SystemFonts.OEM_FONT_DIR)), + new OtfFontFileParser(), new FsverityUtilImpl()); + } + + @NonNull + private SystemFontSettings getCurrentFontSettings() { synchronized (FontManagerService.this) { if (mCurrentFontSettings == null) { mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir); @@ -115,13 +158,14 @@ public final class FontManagerService { } } - private boolean installFontFile(String name, FileDescriptor fd) { + // TODO(b/173619554): Expose as API. + private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) { if (mUpdatableFontDir == null) return false; synchronized (FontManagerService.this) { try { - mUpdatableFontDir.installFontFile(name, fd); + mUpdatableFontDir.installFontFile(fd, pkcs7Signature); } catch (IOException e) { - Slog.w(TAG, "Failed to install font file: " + name, e); + Slog.w(TAG, "Failed to install font file"); return false; } // Create updated font map in the next getSerializedSystemFontMap() call. @@ -194,5 +238,5 @@ public final class FontManagerService { } return null; } - }; + } } 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 7306471e68c2..8da579fd4fa3 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -16,6 +16,7 @@ package com.android.server.graphics.fonts; +import android.annotation.Nullable; import android.os.FileUtils; import android.util.Base64; import android.util.Slog; @@ -28,71 +29,158 @@ import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.util.HashMap; +import java.util.List; import java.util.Map; final class UpdatableFontDir { private static final String TAG = "UpdatableFontDir"; private static final String RANDOM_DIR_PREFIX = "~~"; + // TODO: Support .otf + private static final String ALLOWED_EXTENSION = ".ttf"; /** Interface to mock font file access in tests. */ interface FontFileParser { - long getVersion(File file) throws IOException; + String getPostScriptName(File file) throws IOException; + + long getRevision(File file) throws IOException; + } + + /** Interface to mock fs-verity in tests. */ + interface FsverityUtil { + boolean hasFsverity(String path); + + void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException; + + boolean rename(File src, File dest); } - /** Data class to hold font file path and version. */ - static final class FontFileInfo { - final File mFile; - final long mVersion; + /** Data class to hold font file path and revision. */ + private static final class FontFileInfo { + private final File mFile; + private final long mRevision; - FontFileInfo(File file, long version) { + FontFileInfo(File file, long revision) { mFile = file; - mVersion = version; + mRevision = revision; + } + + public File getFile() { + return mFile; + } + + /** Returns the unique randomized font dir containing this font file. */ + public File getRandomizedFontDir() { + return mFile.getParentFile(); + } + + public long getRevision() { + return mRevision; } } /** - * Root directory for storing updated font files. Each font file is stored in a unique random - * dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. + * Root directory for storing updated font files. Each font file is stored in a unique + * randomized dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. */ private final File mFilesDir; + private final List<File> mPreinstalledFontDirs; private final FontFileParser mParser; + private final FsverityUtil mFsverityUtil; + /** + * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link + * FontFileInfo}. All files in this map are validated, and have higher revision numbers than + * corresponding font files in {@link #mPreinstalledFontDirs}. + */ @GuardedBy("UpdatableFontDir.this") private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>(); - UpdatableFontDir(File filesDir, FontFileParser parser) { + UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, + FsverityUtil fsverityUtil) { mFilesDir = filesDir; + mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; + mFsverityUtil = fsverityUtil; loadFontFileMap(); } private void loadFontFileMap() { + // TODO: SIGBUS crash protection synchronized (UpdatableFontDir.this) { + boolean success = false; mFontFileInfoMap.clear(); - File[] dirs = mFilesDir.listFiles(); - if (dirs == null) return; - for (File dir : dirs) { - if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) continue; - File[] files = dir.listFiles(); - if (files == null || files.length != 1) continue; - addFileToMapLocked(files[0], true); + try { + File[] dirs = mFilesDir.listFiles(); + if (dirs == null) return; + for (File dir : dirs) { + if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return; + File[] files = dir.listFiles(); + if (files == null || files.length != 1) return; + FontFileInfo fontFileInfo = validateFontFile(files[0]); + if (fontFileInfo == null) { + Slog.w(TAG, "Broken file is found. Clearing files."); + return; + } + addFileToMapLocked(fontFileInfo, true /* deleteOldFile */); + } + success = true; + } finally { + // Delete all files just in case if we find a problematic file. + if (!success) { + mFontFileInfoMap.clear(); + FileUtils.deleteContents(mFilesDir); + } } } } - void installFontFile(String name, FileDescriptor fd) throws IOException { - // TODO: Validate name. + /** + * Installs a new font file, or updates an existing font file. + * + * <p>The new font will be immediately available for new Zygote-forked processes through + * {@link #getFontFileMap()}. Old font files will be kept until next system server reboot, + * because existing Zygote-forked processes have paths to old font files. + * + * @param fd A file descriptor to the font file. + * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. + */ + void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException { synchronized (UpdatableFontDir.this) { - // TODO: proper error handling File newDir = getRandomDir(mFilesDir); if (!newDir.mkdir()) { + // TODO: Define and return an error code for API throw new IOException("Failed to create a new dir"); } - File newFontFile = new File(newDir, name); - try (FileOutputStream out = new FileOutputStream(newFontFile)) { - FileUtils.copy(fd, out.getFD()); + boolean success = false; + try { + File tempNewFontFile = new File(newDir, "font.ttf"); + try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) { + FileUtils.copy(fd, out.getFD()); + } + // Do not parse font file before setting up fs-verity. + // setUpFsverity throws IOException if failed. + mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature); + String postScriptName = mParser.getPostScriptName(tempNewFontFile); + File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION); + if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { + // TODO: Define and return an error code for API + throw new IOException("Failed to rename"); + } + FontFileInfo fontFileInfo = validateFontFile(newFontFile); + if (fontFileInfo == null) { + // TODO: Define and return an error code for API + throw new IllegalArgumentException("Invalid file"); + } + if (!addFileToMapLocked(fontFileInfo, false)) { + // TODO: Define and return an error code for API + throw new IllegalArgumentException("Version downgrade"); + } + success = true; + } finally { + if (!success) { + FileUtils.deleteContentsAndDir(newDir); + } } - addFileToMapLocked(newFontFile, false); } } @@ -114,27 +202,110 @@ final class UpdatableFontDir { return dir; } - private void addFileToMapLocked(File file, boolean deleteOldFile) { - final long version; + /** + * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is + * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link + * #mPreinstalledFontDirs}). + */ + private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) { + String name = fontFileInfo.getFile().getName(); + FontFileInfo existingInfo = mFontFileInfoMap.get(name); + final boolean shouldAddToMap; + if (existingInfo == null) { + // We got a new updatable font. We need to check if it's newer than preinstalled fonts. + // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font + // with 'name'. + shouldAddToMap = getPreinstalledFontRevision(name) < fontFileInfo.getRevision(); + } else { + shouldAddToMap = existingInfo.getRevision() < fontFileInfo.getRevision(); + } + if (shouldAddToMap) { + if (deleteOldFile && existingInfo != null) { + FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); + } + mFontFileInfoMap.put(name, fontFileInfo); + return true; + } else { + if (deleteOldFile) { + FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); + } + return false; + } + } + + private long getPreinstalledFontRevision(String name) { + long maxRevision = -1; + for (File dir : mPreinstalledFontDirs) { + File preinstalledFontFile = new File(dir, name); + if (!preinstalledFontFile.exists()) continue; + long revision = getFontRevision(preinstalledFontFile); + if (revision == -1) { + Slog.w(TAG, "Invalid preinstalled font file"); + continue; + } + if (revision > maxRevision) { + maxRevision = revision; + } + } + return maxRevision; + } + + /** + * Checks the fs-verity protection status of the given font file, validates the file name, and + * returns a {@link FontFileInfo} on success. This method does not check if the font revision + * is higher than the currently used font. + */ + @Nullable + private FontFileInfo validateFontFile(File file) { + if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) { + Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file); + return null; + } + if (!validateFontFileName(file)) { + Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file); + return null; + } + long revision = getFontRevision(file); + if (revision == -1) { + Slog.w(TAG, "Font validation failed. Could not read font revision: " + file); + return null; + } + return new FontFileInfo(file, revision); + } + + /** + * Returns true if the font file's file name matches with the PostScript name metadata in the + * font file. + * + * <p>We check the font file names because apps use file name to look up fonts. + * <p>Because PostScript name does not include extension, the extension is appended for + * comparison. For example, if the file name is "NotoColorEmoji.ttf", the PostScript name should + * be "NotoColorEmoji". + */ + private boolean validateFontFileName(File file) { + String fileName = file.getName(); + String postScriptName = getPostScriptName(file); + return (postScriptName + ALLOWED_EXTENSION).equals(fileName); + } + + /** Returns the PostScript name of the given font file, or null. */ + @Nullable + private String getPostScriptName(File file) { try { - version = mParser.getVersion(file); + return mParser.getPostScriptName(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); - return; - } - if (version == -1) { - Slog.e(TAG, "Invalid font file"); - return; + return null; } - FontFileInfo info = mFontFileInfoMap.get(file.getName()); - if (info == null) { - // TODO: check version of font in /system/fonts and /product/fonts - mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); - } else if (info.mVersion < version) { - if (deleteOldFile) { - FileUtils.deleteContentsAndDir(info.mFile.getParentFile()); - } - mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); + } + + /** Returns the non-negative font revision of the given font file, or -1. */ + private long getFontRevision(File file) { + try { + return mParser.getRevision(file); + } catch (IOException e) { + Slog.e(TAG, "Failed to read font file", e); + return -1; } } @@ -142,7 +313,7 @@ final class UpdatableFontDir { Map<String, File> map = new HashMap<>(); synchronized (UpdatableFontDir.this) { for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) { - map.put(entry.getKey(), entry.getValue().mFile); + map.put(entry.getKey(), entry.getValue().getFile()); } } return map; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ab7e3b074940..143ec157119e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -167,6 +167,7 @@ import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.IInputMethodSubtypeListResultCallback; import com.android.internal.inputmethod.IInputMethodSubtypeResultCallback; import com.android.internal.inputmethod.IIntResultCallback; +import com.android.internal.inputmethod.IVoidResultCallback; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; @@ -3720,38 +3721,43 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void showInputMethodPickerFromClient( - IInputMethodClient client, int auxiliarySubtypeMode) { - synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { - return; - } - if(!canShowInputMethodPickerLocked(client)) { - Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " - + Binder.getCallingUid() + ": " + client); - return; - } + public void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } + if (!canShowInputMethodPickerLocked(client)) { + Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " + + Binder.getCallingUid() + ": " + client); + return; + } - // Always call subtype picker, because subtype picker is a superset of input method - // picker. - mHandler.sendMessage(mCaller.obtainMessageII( - MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, - (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY)); - } + // Always call subtype picker, because subtype picker is a superset of input method + // picker. + mHandler.sendMessage(mCaller.obtainMessageII( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, + (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY)); + } + }); } @Override public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode, - int displayId) { - if (mContext.checkCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "showInputMethodPickerFromSystem requires WRITE_SECURE_SETTINGS permission"); - } - // Always call subtype picker, because subtype picker is a superset of input method - // picker. - mHandler.sendMessage(mCaller.obtainMessageII( - MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)); + int displayId, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + if (mContext.checkCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "showInputMethodPickerFromSystem requires WRITE_SECURE_SETTINGS " + + "permission"); + } + // Always call subtype picker, because subtype picker is a superset of input method + // picker. + mHandler.sendMessage(mCaller.obtainMessageII( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)); + }); } /** @@ -3795,15 +3801,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodAndSubtypeEnablerFromClient( - IInputMethodClient client, String inputMethodId) { - synchronized (mMethodMap) { - // TODO(yukawa): Should we verify the display ID? - if (!calledFromValidUserLocked()) { - return; + IInputMethodClient client, String inputMethodId, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + synchronized (mMethodMap) { + // TODO(yukawa): Should we verify the display ID? + if (!calledFromValidUserLocked()) { + return; + } + executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( + MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); } - executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( - MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); - } + }); } @BinderThread @@ -4011,84 +4019,87 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void reportActivityView(IInputMethodClient parentClient, int childDisplayId, - float[] matrixValues) { - final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(childDisplayId); - if (displayInfo == null) { - throw new IllegalArgumentException( - "Cannot find display for non-existent displayId: " + childDisplayId); - } - final int callingUid = Binder.getCallingUid(); - if (callingUid != displayInfo.ownerUid) { - throw new SecurityException("The caller doesn't own the display."); - } - - synchronized (mMethodMap) { - final ClientState cs = mClients.get(parentClient.asBinder()); - if (cs == null) { - return; + float[] matrixValues, IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(childDisplayId); + if (displayInfo == null) { + throw new IllegalArgumentException( + "Cannot find display for non-existent displayId: " + childDisplayId); + } + final int callingUid = Binder.getCallingUid(); + if (callingUid != displayInfo.ownerUid) { + throw new SecurityException("The caller doesn't own the display."); } - // null matrixValues means that the entry needs to be removed. - if (matrixValues == null) { - final ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(childDisplayId); - if (info == null) { + synchronized (mMethodMap) { + final ClientState cs = mClients.get(parentClient.asBinder()); + if (cs == null) { return; } - if (info.mParentClient != cs) { - throw new SecurityException("Only the owner client can clear" - + " ActivityViewGeometry for display #" + childDisplayId); - } - mActivityViewDisplayIdToParentMap.remove(childDisplayId); - return; - } - ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(childDisplayId); - if (info != null && info.mParentClient != cs) { - throw new InvalidParameterException("Display #" + childDisplayId - + " is already registered by " + info.mParentClient); - } - if (info == null) { - if (!mWindowManagerInternal.isUidAllowedOnDisplay(childDisplayId, cs.uid)) { - throw new SecurityException(cs + " cannot access to display #" - + childDisplayId); + // null matrixValues means that the entry needs to be removed. + if (matrixValues == null) { + final ActivityViewInfo info = + mActivityViewDisplayIdToParentMap.get(childDisplayId); + if (info == null) { + return; + } + if (info.mParentClient != cs) { + throw new SecurityException("Only the owner client can clear" + + " ActivityViewGeometry for display #" + childDisplayId); + } + mActivityViewDisplayIdToParentMap.remove(childDisplayId); + return; } - info = new ActivityViewInfo(cs, new Matrix()); - mActivityViewDisplayIdToParentMap.put(childDisplayId, info); - } - info.mMatrix.setValues(matrixValues); - if (mCurClient == null || mCurClient.curSession == null) { - return; - } - - Matrix matrix = null; - int displayId = mCurClient.selfReportedDisplayId; - boolean needToNotify = false; - while (true) { - needToNotify |= (displayId == childDisplayId); - final ActivityViewInfo next = mActivityViewDisplayIdToParentMap.get(displayId); - if (next == null) { - break; + ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(childDisplayId); + if (info != null && info.mParentClient != cs) { + throw new InvalidParameterException("Display #" + childDisplayId + + " is already registered by " + info.mParentClient); } - if (matrix == null) { - matrix = new Matrix(next.mMatrix); - } else { - matrix.postConcat(next.mMatrix); + if (info == null) { + if (!mWindowManagerInternal.isUidAllowedOnDisplay(childDisplayId, cs.uid)) { + throw new SecurityException(cs + " cannot access to display #" + + childDisplayId); + } + info = new ActivityViewInfo(cs, new Matrix()); + mActivityViewDisplayIdToParentMap.put(childDisplayId, info); } - if (next.mParentClient.selfReportedDisplayId == mCurTokenDisplayId) { - if (needToNotify) { - final float[] values = new float[9]; - matrix.getValues(values); - try { - mCurClient.client.updateActivityViewToScreenMatrix(mCurSeq, values); - } catch (RemoteException e) { + info.mMatrix.setValues(matrixValues); + + if (mCurClient == null || mCurClient.curSession == null) { + return; + } + + Matrix matrix = null; + int displayId = mCurClient.selfReportedDisplayId; + boolean needToNotify = false; + while (true) { + needToNotify |= (displayId == childDisplayId); + final ActivityViewInfo next = mActivityViewDisplayIdToParentMap.get(displayId); + if (next == null) { + break; + } + if (matrix == null) { + matrix = new Matrix(next.mMatrix); + } else { + matrix.postConcat(next.mMatrix); + } + if (next.mParentClient.selfReportedDisplayId == mCurTokenDisplayId) { + if (needToNotify) { + final float[] values = new float[9]; + matrix.getValues(values); + try { + mCurClient.client.updateActivityViewToScreenMatrix(mCurSeq, values); + } catch (RemoteException e) { + } } + break; } - break; + displayId = info.mParentClient.selfReportedDisplayId; } - displayId = info.mParentClient.selfReportedDisplayId; } - } + }); } @Override diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 34858814deb0..2dd7096cf763 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -82,6 +82,7 @@ import com.android.internal.inputmethod.IIntResultCallback; import com.android.internal.inputmethod.IMultiClientInputMethod; import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations; import com.android.internal.inputmethod.IMultiClientInputMethodSession; +import com.android.internal.inputmethod.IVoidResultCallback; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -1776,23 +1777,26 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void showInputMethodPickerFromClient( - IInputMethodClient client, int auxiliarySubtypeMode) { + public void showInputMethodPickerFromClient(IInputMethodClient client, + int auxiliarySubtypeMode, IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread @Override - public void showInputMethodPickerFromSystem( - IInputMethodClient client, int auxiliarySubtypeMode, int displayId) { + public void showInputMethodPickerFromSystem(IInputMethodClient client, + int auxiliarySubtypeMode, int displayId, IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread @Override - public void showInputMethodAndSubtypeEnablerFromClient( - IInputMethodClient client, String inputMethodId) { + public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient client, + String inputMethodId, IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread @@ -1825,8 +1829,9 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override public void reportActivityView(IInputMethodClient parentClient, int childDisplayId, - float[] matrixValues) { + float[] matrixValues, IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 9f351a3c3c0d..28c90e965e47 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -88,6 +88,7 @@ import android.os.storage.StorageManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; +import android.security.Authorization; import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; @@ -1268,6 +1269,7 @@ public class LockSettingsService extends ILockSettings.Stub { private void unlockKeystore(byte[] password, int userHandle) { if (DEBUG) Slog.v(TAG, "Unlock keystore for user: " + userHandle); + new Authorization().onLockScreenEvent(false, userHandle, password); // TODO(b/120484642): Update keystore to accept byte[] passwords String passwordString = password == null ? null : new String(password); final KeyStore ks = KeyStore.getInstance(); diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java new file mode 100644 index 000000000000..8399f54764e0 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -0,0 +1,249 @@ +/* + * 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.locksettings; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelableException; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.service.resumeonreboot.IResumeOnRebootService; +import android.service.resumeonreboot.ResumeOnRebootService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** @hide */ +public class ResumeOnRebootServiceProvider { + + private static final String PROVIDER_PACKAGE = DeviceConfig.getString( + DeviceConfig.NAMESPACE_OTA, "resume_on_reboot_service_package", ""); + private static final String PROVIDER_REQUIRED_PERMISSION = + Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; + private static final String TAG = "ResumeOnRebootServiceProvider"; + + private final Context mContext; + private final PackageManager mPackageManager; + + public ResumeOnRebootServiceProvider(Context context) { + this(context, context.getPackageManager()); + } + + @VisibleForTesting + public ResumeOnRebootServiceProvider(Context context, PackageManager packageManager) { + this.mContext = context; + this.mPackageManager = packageManager; + } + + @Nullable + private ServiceInfo resolveService() { + Intent intent = new Intent(); + intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); + if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { + intent.setPackage(PROVIDER_PACKAGE); + } + + List<ResolveInfo> resolvedIntents = + mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); + for (ResolveInfo resolvedInfo : resolvedIntents) { + if (resolvedInfo.serviceInfo != null + && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { + return resolvedInfo.serviceInfo; + } + } + return null; + } + + /** Creates a new {@link ResumeOnRebootServiceConnection} */ + @Nullable + public ResumeOnRebootServiceConnection getServiceConnection() { + ServiceInfo serviceInfo = resolveService(); + if (serviceInfo == null) { + return null; + } + return new ResumeOnRebootServiceConnection(mContext, serviceInfo.getComponentName()); + } + + /** + * Connection class used for contacting the registered {@link IResumeOnRebootService} + */ + public static class ResumeOnRebootServiceConnection { + + private static final String TAG = "ResumeOnRebootServiceConnection"; + private final Context mContext; + private final ComponentName mComponentName; + private IResumeOnRebootService mBinder; + + private ResumeOnRebootServiceConnection(Context context, + @NonNull ComponentName componentName) { + mContext = context; + mComponentName = componentName; + } + + /** Unbind from the service */ + public void unbindService() { + mContext.unbindService(new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBinder = null; + + } + }); + } + + /** Bind to the service */ + public void bindToService(long timeOut) throws TimeoutException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + CountDownLatch connectionLatch = new CountDownLatch(1); + Intent intent = new Intent(); + intent.setComponent(mComponentName); + final boolean success = mContext.bindServiceAsUser(intent, new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mBinder = IResumeOnRebootService.Stub.asInterface(service); + connectionLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + BackgroundThread.getHandler(), UserHandle.SYSTEM); + + if (!success) { + Slog.e(TAG, "Binding: " + mComponentName + " u" + UserHandle.SYSTEM + + " failed."); + return; + } + waitForLatch(connectionLatch, "serviceConnection", timeOut); + } + } + + /** Wrap opaque blob */ + public byte[] wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, + long timeOutInMillis) + throws RemoteException, TimeoutException, IOException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + throw new RemoteException("Service not bound"); + } + CountDownLatch binderLatch = new CountDownLatch(1); + ResumeOnRebootServiceCallback + resultCallback = + new ResumeOnRebootServiceCallback( + binderLatch); + mBinder.wrapSecret(unwrappedBlob, lifeTimeInMillis, new RemoteCallback(resultCallback)); + waitForLatch(binderLatch, "wrapSecret", timeOutInMillis); + if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { + throwTypedException(resultCallback.getResult().getParcelable( + ResumeOnRebootService.EXCEPTION_KEY)); + } + return resultCallback.mResult.getByteArray(ResumeOnRebootService.WRAPPED_BLOB_KEY); + } + + /** Unwrap wrapped blob */ + public byte[] unwrap(byte[] wrappedBlob, long timeOut) + throws RemoteException, TimeoutException, IOException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + throw new RemoteException("Service not bound"); + } + CountDownLatch binderLatch = new CountDownLatch(1); + ResumeOnRebootServiceCallback + resultCallback = + new ResumeOnRebootServiceCallback( + binderLatch); + mBinder.unwrap(wrappedBlob, new RemoteCallback(resultCallback)); + waitForLatch(binderLatch, "unWrapSecret", timeOut); + if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { + throwTypedException(resultCallback.getResult().getParcelable( + ResumeOnRebootService.EXCEPTION_KEY)); + } + return resultCallback.getResult().getByteArray( + ResumeOnRebootService.UNWRAPPED_BLOB_KEY); + } + + private void throwTypedException( + ParcelableException exception) + throws IOException { + if (exception.getCause() instanceof IOException) { + exception.maybeRethrow(IOException.class); + } else if (exception.getCause() instanceof IllegalStateException) { + exception.maybeRethrow(IllegalStateException.class); + } else { + // This should not happen. Wrap the cause in IllegalStateException so that it + // doesn't disrupt the exception handling + throw new IllegalStateException(exception.getCause()); + } + } + + private void waitForLatch(CountDownLatch latch, String reason, long timeOut) + throws TimeoutException { + try { + if (!latch.await(timeOut, TimeUnit.SECONDS)) { + throw new TimeoutException("Latch wait for " + reason + " elapsed"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Latch wait for " + reason + " interrupted"); + } + } + } + + private static class ResumeOnRebootServiceCallback implements + RemoteCallback.OnResultListener { + + private final CountDownLatch mResultLatch; + private Bundle mResult; + + private ResumeOnRebootServiceCallback(CountDownLatch resultLatch) { + this.mResultLatch = resultLatch; + } + + @Override + public void onResult(@Nullable Bundle result) { + this.mResult = result; + mResultLatch.countDown(); + } + + private Bundle getResult() { + return mResult; + } + } +} diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 141fa6a17873..f92f3dcd77ef 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -39,11 +39,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract void resetUserState(int userId); /** - * @return true if the given uid is restricted from doing networking on metered networks. - */ - public abstract boolean isUidRestrictedOnMeteredNetworks(int uid); - - /** * Figure out if networking is blocked for a given set of conditions. * * This is used by ConnectivityService via passing stale copies of conditions, so it must not diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 5ed7a9650f6c..29eaf4fcd7ed 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -72,6 +72,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.net.NetworkPolicyManager.resolveNetworkId; @@ -234,6 +235,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; @@ -3483,13 +3485,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, - long timeoutMillis, String callingPackage) { + int[] networkTypes, long timeoutMillis, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); - // We can only override when carrier told us about plans + final ArraySet<Integer> allNetworksSet = new ArraySet<>(); + addAll(allNetworksSet, TelephonyManager.getAllNetworkTypes()); + final IntArray applicableNetworks = new IntArray(); + + // ensure all network types are valid + for (int networkType : networkTypes) { + if (allNetworksSet.contains(networkType)) { + applicableNetworks.add(networkType); + } else { + Log.d(TAG, "setSubscriptionOverride removing invalid network type: " + networkType); + } + } + + // We can only override when carrier told us about plans. For the unmetered case, + // allow override without having plans defined. synchronized (mNetworkPoliciesSecondLock) { final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId); - if (plan == null + if (overrideMask != SUBSCRIPTION_OVERRIDE_UNMETERED && plan == null || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN) { throw new IllegalStateException( "Must provide valid SubscriptionPlan to enable overriding"); @@ -3501,11 +3517,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(), NETPOLICY_OVERRIDE_ENABLED, 1) != 0; if (overrideEnabled || overrideValue == 0) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, overrideValue, subId)); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = subId; + args.arg2 = overrideMask; + args.arg3 = overrideValue; + args.arg4 = applicableNetworks.toArray(); + mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args)); if (timeoutMillis > 0) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, 0, subId), timeoutMillis); + args.arg3 = 0; + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args), + timeoutMillis); } } } @@ -4773,10 +4794,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId, - int overrideMask, int overrideValue) { + int overrideMask, int overrideValue, int[] networkTypes) { if (listener != null) { try { - listener.onSubscriptionOverride(subId, overrideMask, overrideValue); + listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); } catch (RemoteException ignored) { } } @@ -4908,13 +4929,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return true; } case MSG_SUBSCRIPTION_OVERRIDE: { - final int overrideMask = msg.arg1; - final int overrideValue = msg.arg2; - final int subId = (int) msg.obj; + final SomeArgs args = (SomeArgs) msg.obj; + final int subId = (int) args.arg1; + final int overrideMask = (int) args.arg2; + final int overrideValue = (int) args.arg3; + final int[] networkTypes = (int[]) args.arg4; final int length = mListeners.beginBroadcast(); for (int i = 0; i < length; i++) { final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); - dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue); + dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue, + networkTypes); } mListeners.finishBroadcast(); return true; @@ -5359,7 +5383,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { final long startTime = mStatLogger.getTime(); - enforceAnyPermissionOf(OBSERVE_NETWORK_POLICY, PERMISSION_MAINLINE_NETWORK_STACK); + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); final int uidRules; final boolean isBackgroundRestricted; synchronized (mUidRulesFirstLock) { @@ -5374,6 +5398,23 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return ret; } + @Override + public boolean isUidRestrictedOnMeteredNetworks(int uid) { + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + final int uidRules; + final boolean isBackgroundRestricted; + synchronized (mUidRulesFirstLock) { + uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); + isBackgroundRestricted = mRestrictBackground; + } + //TODO(b/177490332): The logic here might not be correct because it doesn't consider + // RULE_REJECT_METERED condition. And it could be replaced by + // isUidNetworkingBlockedInternal(). + return isBackgroundRestricted + && !hasRule(uidRules, RULE_ALLOW_METERED) + && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); + } + private static boolean isSystem(int uid) { return uid < Process.FIRST_APPLICATION_UID; } @@ -5442,22 +5483,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - /** - * @return true if the given uid is restricted from doing networking on metered networks. - */ - @Override - public boolean isUidRestrictedOnMeteredNetworks(int uid) { - final int uidRules; - final boolean isBackgroundRestricted; - synchronized (mUidRulesFirstLock) { - uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); - isBackgroundRestricted = mRestrictBackground; - } - return isBackgroundRestricted - && !hasRule(uidRules, RULE_ALLOW_METERED) - && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); - } - @Override public void onTempPowerSaveWhitelistChange(int appId, boolean added) { synchronized (mUidRulesFirstLock) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c3977242b5dd..e604b5e6146a 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6432,7 +6432,6 @@ public class NotificationManagerService extends SystemService { private final int mRank; private final int mCount; private final ManagedServiceInfo mListener; - private final long mWhen; CancelNotificationRunnable(final int callingUid, final int callingPid, final String pkg, final String tag, final int id, @@ -6452,7 +6451,6 @@ public class NotificationManagerService extends SystemService { this.mRank = rank; this.mCount = count; this.mListener = listener; - this.mWhen = System.currentTimeMillis(); } @Override @@ -6464,33 +6462,8 @@ public class NotificationManagerService extends SystemService { } synchronized (mNotificationLock) { - // Check to see if there is a notification in the enqueued list that hasn't had a - // chance to post yet. - List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria( - mPkg, mTag, mId, mUserId); - boolean repost = false; - if (enqueued.size() > 0) { - // Found something, let's see what it was - repost = true; - // If all enqueues happened before this cancel then wait for them to happen, - // otherwise we should let this cancel through so the next enqueue happens - for (NotificationRecord r : enqueued) { - if (r.mUpdateTimeMs > mWhen) { - // At least one enqueue was posted after the cancel, so we're invalid - Slog.i(TAG, "notification cancel ignored due to newer enqueued entry" - + "key=" + r.getSbn().getKey()); - return; - } - } - } - if (repost) { - mHandler.post(this); - return; - } - - // Look for the notification in the posted list, since we already checked enqueued. - NotificationRecord r = - findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId); + // Look for the notification, searching both the posted and enqueued lists. + NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId); if (r != null) { // The notification was found, check if it should be removed. @@ -6513,10 +6486,6 @@ public class NotificationManagerService extends SystemService { if ((r.getNotification().flags & mMustNotHaveFlags) != 0) { return; } - if (r.getUpdateTimeMs() > mWhen) { - // In this case, a post must have slipped by when this runnable reposted - return; - } // Bubbled children get to stick around if the summary was manually cancelled // (user removed) from systemui. diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index d7a1ba2a93d4..50fb176e3a5a 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -24,11 +24,15 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_REASON; +import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED; +import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED; import static android.content.pm.PackageManager.SIGNATURE_MATCH; import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; import static android.os.Trace.traceEnd; +import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -40,6 +44,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; +import android.content.om.OverlayManagerTransaction; import android.content.om.OverlayableInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; @@ -49,6 +54,7 @@ import android.content.res.ApkAssets; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -65,7 +71,6 @@ import android.util.SparseArray; import com.android.internal.content.om.OverlayConfig; import com.android.server.FgThread; -import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; @@ -84,12 +89,15 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; /** * Service to manage asset overlays. @@ -238,7 +246,14 @@ public final class OverlayManagerService extends SystemService { private final OverlayActorEnforcer mActorEnforcer; - private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false); + private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> { + persistSettings(); + FgThread.getHandler().post(() -> { + List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId); + updateActivityManager(affectedTargets, pair.userId); + broadcastActionOverlayChanged(pair.packageName, pair.userId); + }); + }; public OverlayManagerService(@NonNull final Context context) { super(context); @@ -251,17 +266,19 @@ public final class OverlayManagerService extends SystemService { IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager); mSettings = new OverlayManagerSettings(); mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings, - OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(), - new OverlayChangeListener()); + OverlayConfig.getSystemInstance(), getDefaultOverlayPackages()); mActorEnforcer = new OverlayActorEnforcer(mPackageManager); + HandlerThread packageReceiverThread = new HandlerThread(TAG); + packageReceiverThread.start(); + final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); packageFilter.addAction(ACTION_PACKAGE_CHANGED); packageFilter.addAction(ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, - packageFilter, null, null); + packageFilter, null, packageReceiverThread.getThreadHandler()); final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(ACTION_USER_ADDED); @@ -294,11 +311,11 @@ public final class OverlayManagerService extends SystemService { for (int i = 0; i < userCount; i++) { final UserInfo userInfo = users.get(i); if (!userInfo.supportsSwitchTo() && userInfo.id != UserHandle.USER_SYSTEM) { - // Initialize any users that can't be switched to, as there state would + // Initialize any users that can't be switched to, as their state would // never be setup in onSwitchUser(). We will switch to the system user right // after this, and its state will be setup there. final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id); - updateOverlayPaths(users.get(i).id, targets); + updatePackageManager(targets, users.get(i).id); } } } @@ -316,9 +333,10 @@ public final class OverlayManagerService extends SystemService { // any asset changes to the rest of the system synchronized (mLock) { final List<String> targets = mImpl.updateOverlaysForUser(newUserId); - updateAssets(newUserId, targets); + final List<String> affectedTargets = updatePackageManager(targets, newUserId); + updateActivityManager(affectedTargets, newUserId); } - schedulePersistSettings(); + persistSettings(); } finally { traceEnd(TRACE_TAG_RRO); } @@ -402,10 +420,17 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageAdded(packageName, userId); - } else { - mImpl.onTargetPackageAdded(packageName, userId); + + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageAdded(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } else { + mImpl.onTargetPackageAdded(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageAdded internal error", e); } } } @@ -425,10 +450,17 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageChanged(packageName, userId); - } else { - mImpl.onTargetPackageChanged(packageName, userId); + + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageChanged(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } else { + mImpl.onTargetPackageChanged(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageChanged internal error", e); } } } @@ -447,7 +479,12 @@ public final class OverlayManagerService extends SystemService { mPackageManager.forgetPackageInfo(packageName, userId); final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId); if (oi != null) { - mImpl.onOverlayPackageReplacing(packageName, userId); + try { + mImpl.onOverlayPackageReplacing(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplacing internal error", e); + } } } } @@ -466,10 +503,16 @@ public final class OverlayManagerService extends SystemService { false); if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); - if (pi.isOverlayPackage()) { - mImpl.onOverlayPackageReplaced(packageName, userId); - } else { - mImpl.onTargetPackageReplaced(packageName, userId); + try { + if (pi.isOverlayPackage()) { + mImpl.onOverlayPackageReplaced(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } else { + mImpl.onTargetPackageReplaced(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplaced internal error", e); } } } @@ -487,10 +530,17 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { mPackageManager.forgetPackageInfo(packageName, userId); final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId); - if (oi != null) { - mImpl.onOverlayPackageRemoved(packageName, userId); - } else { - mImpl.onTargetPackageRemoved(packageName, userId); + + try { + if (oi != null) { + mImpl.onOverlayPackageRemoved(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } else { + mImpl.onTargetPackageRemoved(packageName, userId) + .ifPresent(mPropagateOverlayChange); + } + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageRemoved internal error", e); } } } @@ -513,7 +563,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { targets = mImpl.updateOverlaysForUser(userId); } - updateOverlayPaths(userId, targets); + updatePackageManager(targets, userId); } finally { traceEnd(TRACE_TAG_RRO); } @@ -608,7 +658,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabled(packageName, enable, realUserId); + try { + mImpl.setEnabled(packageName, enable, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -633,8 +689,14 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabledExclusive(packageName, false /* withinCategory */, - realUserId); + try { + mImpl.setEnabledExclusive(packageName, + false /* withinCategory */, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -660,8 +722,14 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setEnabledExclusive(packageName, true /* withinCategory */, - realUserId); + try { + mImpl.setEnabledExclusive(packageName, + true /* withinCategory */, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -687,7 +755,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setPriority(packageName, parentPackageName, realUserId); + try { + mImpl.setPriority(packageName, parentPackageName, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -711,7 +785,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setHighestPriority(packageName, realUserId); + try { + mImpl.setHighestPriority(packageName, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -735,7 +815,13 @@ public final class OverlayManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - return mImpl.setLowestPriority(packageName, realUserId); + try { + mImpl.setLowestPriority(packageName, realUserId) + .ifPresent(mPropagateOverlayChange); + return true; + } catch (OperationFailedException e) { + return false; + } } } finally { Binder.restoreCallingIdentity(ident); @@ -784,6 +870,129 @@ public final class OverlayManagerService extends SystemService { } @Override + public void commit(@NonNull final OverlayManagerTransaction transaction) + throws RemoteException { + try { + traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction); + try { + executeAllRequests(transaction); + } catch (Exception e) { + final long ident = Binder.clearCallingIdentity(); + try { + restoreSettings(); + } finally { + Binder.restoreCallingIdentity(ident); + } + Slog.d(TAG, "commit failed: " + e.getMessage(), e); + throw new SecurityException("commit failed" + + (DEBUG ? ": " + e.getMessage() : "")); + } + } finally { + traceEnd(TRACE_TAG_RRO); + } + } + + private Optional<PackageAndUser> executeRequest( + @NonNull final OverlayManagerTransaction.Request request) throws Exception { + final int realUserId = handleIncomingUser(request.userId, request.typeToString()); + enforceActor(request.packageName, request.typeToString(), realUserId); + + final long ident = Binder.clearCallingIdentity(); + try { + switch (request.type) { + case TYPE_SET_ENABLED: + Optional<PackageAndUser> opt1 = + mImpl.setEnabled(request.packageName, true, request.userId); + Optional<PackageAndUser> opt2 = + mImpl.setHighestPriority(request.packageName, request.userId); + // Both setEnabled and setHighestPriority affected the same + // target package and user: if both return non-empty + // Optionals, they are identical + return opt1.isPresent() ? opt1 : opt2; + case TYPE_SET_DISABLED: + return mImpl.setEnabled(request.packageName, false, request.userId); + default: + throw new IllegalArgumentException("unsupported request: " + request); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction) + throws Exception { + if (DEBUG) { + Slog.d(TAG, "commit " + transaction); + } + if (transaction == null) { + throw new IllegalArgumentException("null transaction"); + } + + // map: userId -> set<package-name>: target packages of overlays in + // this transaction + SparseArray<Set<String>> transactionTargets = new SparseArray<>(); + + // map: userId -> set<package-name>: packages that need to reload + // their resources due to changes to the overlays in this + // transaction + SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>(); + + synchronized (mLock) { + + // execute the requests (as calling user) + for (final OverlayManagerTransaction.Request request : transaction) { + executeRequest(request).ifPresent(target -> { + Set<String> userTargets = transactionTargets.get(target.userId); + if (userTargets == null) { + userTargets = new ArraySet<String>(); + transactionTargets.put(target.userId, userTargets); + } + userTargets.add(target.packageName); + }); + } + + // past the point of no return: the entire transaction has been + // processed successfully, we can no longer fail: continue as + // system_server + final long ident = Binder.clearCallingIdentity(); + try { + persistSettings(); + + // inform the package manager about the new paths + for (int index = 0; index < transactionTargets.size(); index++) { + final int userId = transactionTargets.keyAt(index); + final List<String> affectedTargets = + updatePackageManager(transactionTargets.valueAt(index), userId); + affectedPackagesToUpdate.put(userId, affectedTargets); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } // synchronized (mLock) + + FgThread.getHandler().post(() -> { + final long ident = Binder.clearCallingIdentity(); + try { + // schedule apps to refresh + for (int index = 0; index < affectedPackagesToUpdate.size(); index++) { + final int userId = affectedPackagesToUpdate.keyAt(index); + updateActivityManager(affectedPackagesToUpdate.valueAt(index), userId); + } + + // broadcast the ACTION_OVERLAY_CHANGED intents + for (int index = 0; index < transactionTargets.size(); index++) { + final int userId = transactionTargets.keyAt(index); + for (String pkg: transactionTargets.valueAt(index)) { + broadcastActionOverlayChanged(pkg, userId); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + }); + } + + @Override public void onShellCommand(@NonNull final FileDescriptor in, @NonNull final FileDescriptor out, @NonNull final FileDescriptor err, @NonNull final String[] args, @NonNull final ShellCallback callback, @@ -904,162 +1113,7 @@ public final class OverlayManagerService extends SystemService { } }; - private final class OverlayChangeListener - implements OverlayManagerServiceImpl.OverlayChangeListener { - @Override - public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) { - schedulePersistSettings(); - FgThread.getHandler().post(() -> { - updateAssets(userId, targetPackageName); - - final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, - Uri.fromParts("package", targetPackageName, null)); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - if (DEBUG) { - Slog.d(TAG, "send broadcast " + intent); - } - - try { - ActivityManager.getService().broadcastIntentWithFeature(null, null, intent, - null, null, 0, null, null, null, android.app.AppOpsManager.OP_NONE, - null, false, false, userId); - } catch (RemoteException e) { - // Intentionally left empty. - } - }); - } - } - - /** - * Updates the target packages' set of enabled overlays in PackageManager. - */ - private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames); - if (DEBUG) { - Slog.d(TAG, "Updating overlay assets"); - } - final PackageManagerInternal pm = - LocalServices.getService(PackageManagerInternal.class); - final boolean updateFrameworkRes = targetPackageNames.contains("android"); - if (updateFrameworkRes) { - targetPackageNames = pm.getTargetPackageNames(userId); - } - - final Map<String, List<String>> pendingChanges = - new ArrayMap<>(targetPackageNames.size()); - synchronized (mLock) { - final List<String> frameworkOverlays = - mImpl.getEnabledOverlayPackageNames("android", userId); - final int n = targetPackageNames.size(); - for (int i = 0; i < n; i++) { - final String targetPackageName = targetPackageNames.get(i); - List<String> list = new ArrayList<>(); - if (!"android".equals(targetPackageName)) { - list.addAll(frameworkOverlays); - } - list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); - pendingChanges.put(targetPackageName, list); - } - } - - final HashSet<String> updatedPackages = new HashSet<>(); - final int n = targetPackageNames.size(); - for (int i = 0; i < n; i++) { - final String targetPackageName = targetPackageNames.get(i); - if (DEBUG) { - Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" - + TextUtils.join(",", pendingChanges.get(targetPackageName)) - + "] userId=" + userId); - } - - if (!pm.setEnabledOverlayPackages( - userId, targetPackageName, pendingChanges.get(targetPackageName), - updatedPackages)) { - Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", - targetPackageName, userId)); - } - } - return new ArrayList<>(updatedPackages); - } finally { - traceEnd(TRACE_TAG_RRO); - } - } - - private void updateAssets(final int userId, final String targetPackageName) { - updateAssets(userId, Collections.singletonList(targetPackageName)); - } - - private void updateAssets(final int userId, List<String> targetPackageNames) { - final IActivityManager am = ActivityManager.getService(); - try { - final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames); - am.scheduleApplicationInfoChanged(updatedPaths, userId); - } catch (RemoteException e) { - // Intentionally left empty. - } - } - - private void schedulePersistSettings() { - if (mPersistSettingsScheduled.getAndSet(true)) { - return; - } - IoThread.getHandler().post(() -> { - mPersistSettingsScheduled.set(false); - if (DEBUG) { - Slog.d(TAG, "Writing overlay settings"); - } - synchronized (mLock) { - FileOutputStream stream = null; - try { - stream = mSettingsFile.startWrite(); - mSettings.persist(stream); - mSettingsFile.finishWrite(stream); - } catch (IOException | XmlPullParserException e) { - mSettingsFile.failWrite(stream); - Slog.e(TAG, "failed to persist overlay state", e); - } - } - }); - } - - private void restoreSettings() { - try { - traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings"); - synchronized (mLock) { - if (!mSettingsFile.getBaseFile().exists()) { - return; - } - try (FileInputStream stream = mSettingsFile.openRead()) { - mSettings.restore(stream); - - // We might have data for dying users if the device was - // restarted before we received USER_REMOVED. Remove data for - // users that will not exist after the system is ready. - - final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/); - final int[] liveUserIds = new int[liveUsers.size()]; - for (int i = 0; i < liveUsers.size(); i++) { - liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier(); - } - Arrays.sort(liveUserIds); - - for (int userId : mSettings.getUsers()) { - if (Arrays.binarySearch(liveUserIds, userId) < 0) { - mSettings.removeUser(userId); - } - } - } catch (IOException | XmlPullParserException e) { - Slog.e(TAG, "failed to restore overlay state", e); - } - } - } finally { - traceEnd(TRACE_TAG_RRO); - } - } - - private static final class PackageManagerHelperImpl implements PackageManagerHelper { + private static final class PackageManagerHelperImpl implements PackageManagerHelper { private final Context mContext; private final IPackageManager mPackageManager; @@ -1269,4 +1323,144 @@ public final class OverlayManagerService extends SystemService { } } } + + // Helper methods to update other parts of the system or read/write + // settings: these methods should never call into each other! + + private void broadcastActionOverlayChanged(@NonNull final String targetPackageName, + final int userId) { + final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, + Uri.fromParts("package", targetPackageName, null)); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + try { + ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null, + null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); + } catch (RemoteException e) { + // Intentionally left empty. + } + } + + /** + * Tell the activity manager to tell a set of packages to reload their + * resources. + */ + private void updateActivityManager(List<String> targetPackageNames, final int userId) { + final IActivityManager am = ActivityManager.getService(); + try { + am.scheduleApplicationInfoChanged(targetPackageNames, userId); + } catch (RemoteException e) { + // Intentionally left empty. + } + } + + private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) { + return updatePackageManager(Collections.singletonList(targetPackageNames), userId); + } + + /** + * Updates the target packages' set of enabled overlays in PackageManager. + * @return the package names of affected targets (a superset of + * targetPackageNames: the target themserlves and shared libraries) + */ + private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames, + final int userId) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames); + if (DEBUG) { + Slog.d(TAG, "Update package manager about changed overlays"); + } + final PackageManagerInternal pm = + LocalServices.getService(PackageManagerInternal.class); + final boolean updateFrameworkRes = targetPackageNames.contains("android"); + if (updateFrameworkRes) { + targetPackageNames = pm.getTargetPackageNames(userId); + } + + final Map<String, List<String>> pendingChanges = + new ArrayMap<>(targetPackageNames.size()); + synchronized (mLock) { + final List<String> frameworkOverlays = + mImpl.getEnabledOverlayPackageNames("android", userId); + for (final String targetPackageName : targetPackageNames) { + List<String> list = new ArrayList<>(); + if (!"android".equals(targetPackageName)) { + list.addAll(frameworkOverlays); + } + list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); + pendingChanges.put(targetPackageName, list); + } + } + + final HashSet<String> updatedPackages = new HashSet<>(); + for (final String targetPackageName : targetPackageNames) { + if (DEBUG) { + Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" + + TextUtils.join(",", pendingChanges.get(targetPackageName)) + + "] userId=" + userId); + } + + if (!pm.setEnabledOverlayPackages( + userId, targetPackageName, pendingChanges.get(targetPackageName), + updatedPackages)) { + Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", + targetPackageName, userId)); + } + } + return new ArrayList<>(updatedPackages); + } finally { + traceEnd(TRACE_TAG_RRO); + } + } + + private void persistSettings() { + if (DEBUG) { + Slog.d(TAG, "Writing overlay settings"); + } + synchronized (mLock) { + FileOutputStream stream = null; + try { + stream = mSettingsFile.startWrite(); + mSettings.persist(stream); + mSettingsFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + mSettingsFile.failWrite(stream); + Slog.e(TAG, "failed to persist overlay state", e); + } + } + } + + private void restoreSettings() { + try { + traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings"); + synchronized (mLock) { + if (!mSettingsFile.getBaseFile().exists()) { + return; + } + try (FileInputStream stream = mSettingsFile.openRead()) { + mSettings.restore(stream); + + // We might have data for dying users if the device was + // restarted before we received USER_REMOVED. Remove data for + // users that will not exist after the system is ready. + + final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/); + final int[] liveUserIds = new int[liveUsers.size()]; + for (int i = 0; i < liveUsers.size(); i++) { + liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier(); + } + Arrays.sort(liveUserIds); + + for (int userId : mSettings.getUsers()) { + if (Arrays.binarySearch(liveUserIds, userId) < 0) { + mSettings.removeUser(userId); + } + } + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "failed to restore overlay state", e); + } + } + } finally { + traceEnd(TRACE_TAG_RRO); + } + } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 05a4a38feef1..e60411bb78c5 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -45,6 +45,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; /** @@ -71,7 +72,6 @@ final class OverlayManagerServiceImpl { private final OverlayManagerSettings mSettings; private final OverlayConfig mOverlayConfig; private final String[] mDefaultOverlays; - private final OverlayChangeListener mListener; /** * Helper method to merge the overlay manager's (as read from overlays.xml) @@ -114,14 +114,12 @@ final class OverlayManagerServiceImpl { @NonNull final IdmapManager idmapManager, @NonNull final OverlayManagerSettings settings, @NonNull final OverlayConfig overlayConfig, - @NonNull final String[] defaultOverlays, - @NonNull final OverlayChangeListener listener) { + @NonNull final String[] defaultOverlays) { mPackageManager = packageManager; mIdmapManager = idmapManager; mSettings = settings; mOverlayConfig = overlayConfig; mDefaultOverlays = defaultOverlays; - mListener = listener; } /** @@ -259,52 +257,58 @@ final class OverlayManagerServiceImpl { mSettings.removeUser(userId); } - void onTargetPackageAdded(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageChanged(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageReplacing(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageReplaced(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } - void onTargetPackageRemoved(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId); } - updateAndRefreshOverlaysForTarget(packageName, userId, 0); + return updateAndRefreshOverlaysForTarget(packageName, userId, 0); } /** * Update the state of any overlays for this target. */ - private void updateAndRefreshOverlaysForTarget(@NonNull final String targetPackageName, - final int userId, final int flags) { + private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget( + @NonNull final String targetPackageName, final int userId, final int flags) + throws OperationFailedException { final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName, userId); @@ -364,11 +368,13 @@ final class OverlayManagerServiceImpl { } if (modified) { - mListener.onOverlaysChanged(targetPackageName, userId); + return Optional.of(new PackageAndUser(targetPackageName, userId)); } + return Optional.empty(); } - void onOverlayPackageAdded(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId); } @@ -376,8 +382,7 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found"); - onOverlayPackageRemoved(packageName, userId); - return; + return onOverlayPackageRemoved(packageName, userId); } mSettings.init(packageName, userId, overlayPackage.overlayTarget, @@ -389,15 +394,17 @@ final class OverlayManagerServiceImpl { overlayPackage.overlayCategory); try { if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); mSettings.remove(packageName, userId); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageChanged(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId); } @@ -405,14 +412,16 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); if (updateState(oi.targetPackageName, packageName, userId, 0)) { - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageReplacing(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId=" + userId); @@ -423,14 +432,16 @@ final class OverlayManagerServiceImpl { if (updateState(oi.targetPackageName, packageName, userId, FLAG_OVERLAY_IS_BEING_REPLACED)) { removeIdmapIfPossible(oi); - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageReplaced(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId=" + userId); @@ -439,16 +450,12 @@ final class OverlayManagerServiceImpl { final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId); if (pkg == null) { Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found"); - onOverlayPackageRemoved(packageName, userId); - return; + return onOverlayPackageRemoved(packageName, userId); } try { final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId); if (mustReinitializeOverlay(pkg, oldOi)) { - if (oldOi != null && !oldOi.targetPackageName.equals(pkg.overlayTarget)) { - mListener.onOverlaysChanged(pkg.overlayTarget, userId); - } mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName, pkg.applicationInfo.getBaseCodePath(), isPackageConfiguredMutable(pkg.packageName), @@ -457,22 +464,25 @@ final class OverlayManagerServiceImpl { } if (updateState(pkg.overlayTarget, packageName, userId, 0)) { - mListener.onOverlaysChanged(pkg.overlayTarget, userId); + return Optional.of(new PackageAndUser(pkg.overlayTarget, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); + throw new OperationFailedException("failed to update settings", e); } } - void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName, + final int userId) throws OperationFailedException { try { final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId); if (mSettings.remove(packageName, userId)) { removeIdmapIfPossible(overlayInfo); - mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId); + return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId)); } + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to remove overlay", e); + throw new OperationFailedException("failed to remove overlay", e); } } @@ -493,8 +503,8 @@ final class OverlayManagerServiceImpl { return mSettings.getOverlaysForUser(userId); } - boolean setEnabled(@NonNull final String packageName, final boolean enable, - final int userId) { + Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d", packageName, enable, userId)); @@ -502,30 +512,33 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException( + String.format("failed to find overlay package %s for user %d", + packageName, userId)); } try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); if (!oi.isMutable) { // Ignore immutable overlays. - return false; + throw new OperationFailedException( + "cannot enable immutable overlay packages in runtime"); } boolean modified = mSettings.setEnabled(packageName, userId, enable); modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0); if (modified) { - mListener.onOverlaysChanged(oi.targetPackageName, userId); + return Optional.of(new PackageAndUser(oi.targetPackageName, userId)); } - return true; + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - return false; + throw new OperationFailedException("failed to update settings", e); } } - boolean setEnabledExclusive(@NonNull final String packageName, boolean withinCategory, - final int userId) { + Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName, + boolean withinCategory, final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabledExclusive packageName=%s" + " withinCategory=%s userId=%d", packageName, withinCategory, userId)); @@ -533,7 +546,8 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } try { @@ -576,11 +590,11 @@ final class OverlayManagerServiceImpl { modified |= updateState(targetPackageName, packageName, userId, 0); if (modified) { - mListener.onOverlaysChanged(targetPackageName, userId); + return Optional.of(new PackageAndUser(targetPackageName, userId)); } - return true; + return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { - return false; + throw new OperationFailedException("failed to update settings", e); } } @@ -596,66 +610,75 @@ final class OverlayManagerServiceImpl { return mOverlayConfig.isEnabled(packageName); } - boolean setPriority(@NonNull final String packageName, - @NonNull final String newParentPackageName, final int userId) { + Optional<PackageAndUser> setPriority(@NonNull final String packageName, + @NonNull final String newParentPackageName, final int userId) + throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName=" + newParentPackageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setPriority(packageName, newParentPackageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } - boolean setHighestPriority(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName, + final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setHighestPriority(packageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } - boolean setLowestPriority(@NonNull final String packageName, final int userId) { + Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId) + throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId); } if (!isPackageConfiguredMutable(packageName)) { - return false; + throw new OperationFailedException(String.format( + "overlay package %s user %d is not updatable", packageName, userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); if (overlayPackage == null) { - return false; + throw new OperationFailedException(String.format( + "failed to find overlay package %s for user %d", packageName, userId)); } if (mSettings.setLowestPriority(packageName, userId)) { - mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); + return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId)); } - return true; + return Optional.empty(); } void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) { @@ -797,12 +820,13 @@ final class OverlayManagerServiceImpl { mIdmapManager.removeIdmap(oi, oi.userId); } - interface OverlayChangeListener { + static final class OperationFailedException extends Exception { + OperationFailedException(@NonNull final String message) { + super(message); + } - /** - * An event triggered by changes made to overlay state or settings as well as changes that - * add or remove target packages of overlays. - **/ - void onOverlaysChanged(@NonNull String targetPackage, int userId); + OperationFailedException(@NonNull final String message, @NonNull Throwable cause) { + super(message, cause); + } } } diff --git a/services/core/java/com/android/server/om/PackageAndUser.java b/services/core/java/com/android/server/om/PackageAndUser.java new file mode 100644 index 000000000000..5c38ba7ce97b --- /dev/null +++ b/services/core/java/com/android/server/om/PackageAndUser.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.om; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; + +final class PackageAndUser { + public final @NonNull String packageName; + public final @UserIdInt int userId; + + PackageAndUser(@NonNull String packageName, @UserIdInt int userId) { + this.packageName = packageName; + this.userId = userId; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PackageAndUser)) { + return false; + } + PackageAndUser other = (PackageAndUser) obj; + return packageName.equals(other.packageName) && userId == other.userId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + packageName.hashCode(); + result = prime * result + userId; + return result; + } + + @Override + public String toString() { + return String.format("PackageAndUser{packageName=%s, userId=%d}", packageName, userId); + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7952c255fd57..bdff19bbd7bc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -15071,8 +15071,12 @@ public class PackageManagerService extends IPackageManager.Stub } final IActivityManager am = ActivityManager.getService(); try { - final long duration = LocalServices.getService(ActivityManagerInternal.class) - .getBootTimeTempAllowListDuration(); + long duration = 10_000; + final ActivityManagerInternal amInternal = + LocalServices.getService(ActivityManagerInternal.class); + if (amInternal != null) { + duration = amInternal.getBootTimeTempAllowListDuration(); + } final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); bOptions.setTemporaryAppWhitelistDuration( BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, @@ -21419,7 +21423,7 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager.onPackageInstalled(pkg, PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, userId); if (applyUserRestrictions) { - mSettings.writeRuntimePermissionsForUserLPr(userId, false); + mSettings.writePermissionStateForUserLPr(userId, false); } } @@ -27425,7 +27429,7 @@ public class PackageManagerService extends IPackageManager.Stub public void writePermissionSettings(int[] userIds, boolean async) { synchronized (mLock) { for (int userId : userIds) { - mSettings.writeRuntimePermissionsForUserLPr(userId, !async); + mSettings.writePermissionStateForUserLPr(userId, !async); } } } @@ -28065,13 +28069,12 @@ public class PackageManagerService extends IPackageManager.Stub /** * Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's - * writeLegacyPermissionsTEMP() and writeLegacyPermissionStateTEMP() beforehand. + * writeLegacyPermissionsTEMP() beforehand. * * TODO(zhanghai): This should be removed once we finish migration of permission storage. */ private void writeSettingsLPrTEMP() { mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions); - mPermissionManager.writeLegacyPermissionStateTEMP(); mSettings.writeLPr(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 9eae1174fb73..446342a8f512 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2732,7 +2732,7 @@ class PackageManagerShellCommand extends ShellCommand { private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use."); - int result = um.removeUserOrSetEphemeral(userId); + int result = um.removeUserOrSetEphemeral(userId, /* evenWhenDisallowed= */ false); switch (result) { case UserManager.REMOVE_RESULT_REMOVED: getOutPrintWriter().printf("Success: user %d removed\n", userId); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 83f6c5285874..ade087be30c9 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -356,7 +356,7 @@ public class PackageSetting extends PackageSettingBase { proto.write(PackageProto.UserPermissionsProto.ID, user.id); runtimePermissionStates = dataProvider.getLegacyPermissionState(appId) - .getRuntimePermissionStates(user.id); + .getPermissionStates(user.id); for (LegacyPermissionState.PermissionState permission : runtimePermissionStates) { if (permission.isGranted()) { proto.write(PackageProto.UserPermissionsProto.GRANTED_PERMISSIONS, diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 50c1065a6000..34161473a18f 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2313,7 +2313,8 @@ public final class Settings implements Watchable, Snappable { } void readInstallPermissionsLPr(TypedXmlPullParser parser, - LegacyPermissionState permissionsState) throws IOException, XmlPullParserException { + LegacyPermissionState permissionsState, List<UserInfo> users) + throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -2328,8 +2329,10 @@ public final class Settings implements Watchable, Snappable { String name = parser.getAttributeValue(null, ATTR_NAME); final boolean granted = parser.getAttributeBoolean(null, ATTR_GRANTED, true); final int flags = parser.getAttributeIntHex(null, ATTR_FLAGS, 0); - permissionsState.putInstallPermissionState(new PermissionState(name, granted, - flags)); + for (final UserInfo user : users) { + permissionsState.putPermissionState(new PermissionState(name, false, granted, + flags), user.id); + } } else { Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: " + parser.getName()); @@ -2338,25 +2341,6 @@ public final class Settings implements Watchable, Snappable { } } - void writePermissionsLPr(TypedXmlSerializer serializer, Collection<PermissionState> permissionStates) - throws IOException { - if (permissionStates.isEmpty()) { - return; - } - - serializer.startTag(null, TAG_PERMISSIONS); - - for (PermissionState permissionState : permissionStates) { - serializer.startTag(null, TAG_ITEM); - serializer.attributeInterned(null, ATTR_NAME, permissionState.getName()); - serializer.attributeBoolean(null, ATTR_GRANTED, permissionState.isGranted()); - serializer.attributeIntHex(null, ATTR_FLAGS, permissionState.getFlags()); - serializer.endTag(null, TAG_ITEM); - } - - serializer.endTag(null, TAG_PERMISSIONS); - } - void readUsesStaticLibLPw(TypedXmlPullParser parser, PackageSetting outPs) throws IOException, XmlPullParserException { String libName = parser.getAttributeValue(null, ATTR_NAME); @@ -2572,8 +2556,6 @@ public final class Settings implements Watchable, Snappable { serializer.attribute(null, ATTR_NAME, usr.name); serializer.attributeInt(null, "userId", usr.userId); usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - writePermissionsLPr(serializer, usr.getLegacyPermissionState() - .getInstallPermissionStates()); serializer.endTag(null, "shared-user"); } @@ -2898,12 +2880,6 @@ public final class Settings implements Watchable, Snappable { writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions); - // If this is a shared user, the permissions will be written there. - if (pkg.sharedUser == null) { - writePermissionsLPr(serializer, pkg.getLegacyPermissionState() - .getInstallPermissionStates()); - } - serializer.endTag(null, "updated-package"); } @@ -2991,9 +2967,6 @@ public final class Settings implements Watchable, Snappable { serializer, "install-initiator-sigs", mPastSignatures); } - writePermissionsLPr(serializer, - pkg.getLegacyPermissionState().getInstallPermissionStates()); - writeSigningKeySetLPr(serializer, pkg.keySetData); writeUpgradeKeySetsLPr(serializer, pkg.keySetData); writeKeySetAliasesLPr(serializer, pkg.keySetData); @@ -3097,13 +3070,13 @@ public final class Settings implements Watchable, Snappable { String tagName = parser.getName(); if (tagName.equals("package")) { - readPackageLPw(parser); + readPackageLPw(parser, users); } else if (tagName.equals("permissions")) { mPermissions.readPermissions(parser); } else if (tagName.equals("permission-trees")) { mPermissions.readPermissionTrees(parser); } else if (tagName.equals("shared-user")) { - readSharedUserLPw(parser); + readSharedUserLPw(parser, users); } else if (tagName.equals("preferred-packages")) { // no longer used. } else if (tagName.equals("preferred-activities")) { @@ -3121,7 +3094,7 @@ public final class Settings implements Watchable, Snappable { } else if (tagName.equals(TAG_DEFAULT_BROWSER)) { readDefaultAppsLPw(parser, 0); } else if (tagName.equals("updated-package")) { - readDisabledSysPackageLPw(parser); + readDisabledSysPackageLPw(parser, users); } else if (tagName.equals("renamed-package")) { String nname = parser.getAttributeValue(null, "new"); String oname = parser.getAttributeValue(null, "old"); @@ -3627,7 +3600,7 @@ public final class Settings implements Watchable, Snappable { } } - private void readDisabledSysPackageLPw(TypedXmlPullParser parser) + private void readDisabledSysPackageLPw(TypedXmlPullParser parser, List<UserInfo> users) throws XmlPullParserException, IOException { String name = parser.getAttributeValue(null, ATTR_NAME); String realName = parser.getAttributeValue(null, "realName"); @@ -3676,7 +3649,7 @@ public final class Settings implements Watchable, Snappable { } if (parser.getName().equals(TAG_PERMISSIONS)) { - readInstallPermissionsLPr(parser, ps.getLegacyPermissionState()); + readInstallPermissionsLPr(parser, ps.getLegacyPermissionState(), users); } else if (parser.getName().equals(TAG_USES_STATIC_LIB)) { readUsesStaticLibLPw(parser, ps); } else { @@ -3693,7 +3666,7 @@ public final class Settings implements Watchable, Snappable { private static int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1<<28; private static int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1<<30; - private void readPackageLPw(TypedXmlPullParser parser) + private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users) throws XmlPullParserException, IOException { String name = null; String realName = null; @@ -3935,7 +3908,7 @@ public final class Settings implements Watchable, Snappable { packageSetting.signatures.readXml(parser, mPastSignatures); } else if (tagName.equals(TAG_PERMISSIONS)) { readInstallPermissionsLPr(parser, - packageSetting.getLegacyPermissionState()); + packageSetting.getLegacyPermissionState(), users); packageSetting.installPermissionsFixed = true; } else if (tagName.equals("proper-signing-keyset")) { long id = parser.getAttributeLong(null, "identifier"); @@ -4112,7 +4085,7 @@ public final class Settings implements Watchable, Snappable { } } - private void readSharedUserLPw(TypedXmlPullParser parser) + private void readSharedUserLPw(TypedXmlPullParser parser, List<UserInfo> users) throws XmlPullParserException, IOException { String name = null; int pkgFlags = 0; @@ -4156,7 +4129,7 @@ public final class Settings implements Watchable, Snappable { if (tagName.equals("sigs")) { su.signatures.readXml(parser, mPastSignatures); } else if (tagName.equals("perms")) { - readInstallPermissionsLPr(parser, su.getLegacyPermissionState()); + readInstallPermissionsLPr(parser, su.getLegacyPermissionState(), users); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <shared-user>: " + parser.getName()); @@ -4979,7 +4952,7 @@ public final class Settings implements Watchable, Snappable { dumpGidsLPr(pw, prefix + " ", mPermissionDataProvider.getGidsForUid( UserHandle.getUid(user.id, ps.appId))); dumpRuntimePermissionsLPr(pw, prefix + " ", permissionNames, permissionsState - .getRuntimePermissionStates(user.id), dumpAll); + .getPermissionStates(user.id), dumpAll); } String harmfulAppWarning = ps.getHarmfulAppWarning(user.id); @@ -5154,7 +5127,7 @@ public final class Settings implements Watchable, Snappable { final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid( userId, su.userId)); final Collection<PermissionState> permissions = - permissionsState.getRuntimePermissionStates(userId); + permissionsState.getPermissionStates(userId); if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": "); dumpGidsLPr(pw, prefix + " ", gids); @@ -5215,9 +5188,19 @@ public final class Settings implements Watchable, Snappable { void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames, Collection<PermissionState> permissionStates, boolean dumpAll) { - if (!permissionStates.isEmpty() || dumpAll) { + boolean hasRuntimePermissions = false; + for (PermissionState permissionState : permissionStates) { + if (permissionState.isRuntime()) { + hasRuntimePermissions = true; + break; + } + } + if (hasRuntimePermissions || dumpAll) { pw.print(prefix); pw.println("runtime permissions:"); for (PermissionState permissionState : permissionStates) { + if (!permissionState.isRuntime()) { + continue; + } if (permissionNames != null && !permissionNames.contains(permissionState.getName())) { continue; @@ -5256,11 +5239,21 @@ public final class Settings implements Watchable, Snappable { void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames, LegacyPermissionState permissionsState) { - Collection<PermissionState> permissionStates = - permissionsState.getInstallPermissionStates(); - if (!permissionStates.isEmpty()) { + Collection<PermissionState> permissionStates = permissionsState.getPermissionStates( + UserHandle.USER_SYSTEM); + boolean hasInstallPermissions = false; + for (PermissionState permissionState : permissionStates) { + if (!permissionState.isRuntime()) { + hasInstallPermissions = true; + break; + } + } + if (hasInstallPermissions) { pw.print(prefix); pw.println("install permissions:"); for (PermissionState permissionState : permissionStates) { + if (permissionState.isRuntime()) { + continue; + } if (permissionNames != null && !permissionNames.contains(permissionState.getName())) { continue; @@ -5295,7 +5288,7 @@ public final class Settings implements Watchable, Snappable { } } - public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { + public void writePermissionStateForUserLPr(int userId, boolean sync) { if (sync) { mRuntimePermissionsPersistence.writeStateForUserSyncLPr(userId); } else { @@ -5530,7 +5523,7 @@ public final class Settings implements Watchable, Snappable { private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState( @NonNull LegacyPermissionState permissionsState, @UserIdInt int userId) { Collection<PermissionState> permissionStates = - permissionsState.getRuntimePermissionStates(userId); + permissionsState.getPermissionStates(userId); List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>(); for (PermissionState permissionState : permissionStates) { RuntimePermissionsState.PermissionState permission = @@ -5590,6 +5583,7 @@ public final class Settings implements Watchable, Snappable { if (permissions != null) { readPermissionsStateLpr(permissions, packageSetting.getLegacyPermissionState(), userId); + packageSetting.installPermissionsFixed = true; } else if (packageSetting.sharedUser == null && !isUpgradeToR) { Slog.w(TAG, "Missing permission state for package: " + packageName); packageSetting.getLegacyPermissionState().setMissing(true, userId); @@ -5624,7 +5618,7 @@ public final class Settings implements Watchable, Snappable { String name = permission.getName(); boolean granted = permission.isGranted(); int flags = permission.getFlags(); - permissionsState.putRuntimePermissionState(new PermissionState(name, granted, + permissionsState.putPermissionState(new PermissionState(name, true, granted, flags), userId); } } @@ -5646,7 +5640,7 @@ public final class Settings implements Watchable, Snappable { try { final TypedXmlPullParser parser = Xml.resolvePullParser(in); - parseRuntimePermissionsLPr(parser, userId); + parseLegacyRuntimePermissionsLPr(parser, userId); } catch (XmlPullParserException | IOException e) { throw new IllegalStateException("Failed parsing permissions file: " @@ -5659,7 +5653,7 @@ public final class Settings implements Watchable, Snappable { // Private internals @GuardedBy("Settings.this.mLock") - private void parseRuntimePermissionsLPr(TypedXmlPullParser parser, int userId) + private void parseLegacyRuntimePermissionsLPr(TypedXmlPullParser parser, int userId) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); int type; @@ -5687,7 +5681,7 @@ public final class Settings implements Watchable, Snappable { XmlUtils.skipCurrentTag(parser); continue; } - parsePermissionsLPr(parser, ps.getLegacyPermissionState(), userId); + parseLegacyPermissionsLPr(parser, ps.getLegacyPermissionState(), userId); } break; case TAG_SHARED_USER: { @@ -5698,13 +5692,13 @@ public final class Settings implements Watchable, Snappable { XmlUtils.skipCurrentTag(parser); continue; } - parsePermissionsLPr(parser, sus.getLegacyPermissionState(), userId); + parseLegacyPermissionsLPr(parser, sus.getLegacyPermissionState(), userId); } break; } } } - private void parsePermissionsLPr(TypedXmlPullParser parser, + private void parseLegacyPermissionsLPr(TypedXmlPullParser parser, LegacyPermissionState permissionsState, int userId) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); @@ -5722,7 +5716,7 @@ public final class Settings implements Watchable, Snappable { parser.getAttributeBoolean(null, ATTR_GRANTED, true); final int flags = parser.getAttributeIntHex(null, ATTR_FLAGS, 0); - permissionsState.putRuntimePermissionState(new PermissionState(name, + permissionsState.putPermissionState(new PermissionState(name, true, granted, flags), userId); } break; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 19a94b39ea20..314510b89cb9 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3843,7 +3843,6 @@ public class UserManagerService extends IUserManager.Stub { */ @Override public boolean removeUser(@UserIdInt int userId) { - Slog.i(LOG_TAG, "removeUser u" + userId, new Exception()); checkManageOrCreateUsersPermission("Only the system can remove users"); final String restriction = getUserRemovalRestriction(userId); @@ -3968,13 +3967,16 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) { - Slog.i(LOG_TAG, "removeUserOrSetEphemeral u" + userId); + public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId, + boolean evenWhenDisallowed) { checkManageOrCreateUsersPermission("Only the system can remove users"); - final String restriction = getUserRemovalRestriction(userId); - if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { - Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); - return UserManager.REMOVE_RESULT_ERROR; + + if (!evenWhenDisallowed) { + final String restriction = getUserRemovalRestriction(userId); + if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { + Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); + return UserManager.REMOVE_RESULT_ERROR; + } } if (userId == UserHandle.USER_SYSTEM) { Slog.e(LOG_TAG, "System user cannot be removed."); @@ -4003,7 +4005,7 @@ public class UserManagerService extends IUserManager.Stub { final int currentUser = ActivityManager.getCurrentUser(); if (currentUser != userId) { // Attempt to remove the user. This will fail if the user is the current user - if (removeUser(userId)) { + if (removeUserUnchecked(userId)) { return UserManager.REMOVE_RESULT_REMOVED; } } diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java index 72d76285fe40..92f22a4a7b8e 100644 --- a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java +++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java @@ -19,7 +19,6 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -105,28 +104,14 @@ public final class LegacyPermissionState { } /** - * Put a install permission state. - * - * @param permissionState the permission state - */ - public void putInstallPermissionState(@NonNull PermissionState permissionState) { - putPermissionState(permissionState, UserHandle.USER_ALL); - } - - /** - * Put a runtime permission state for a user. + * Put a permission state for a user. * * @param permissionState the permission state * @param userId the user ID */ - public void putRuntimePermissionState(@NonNull PermissionState permissionState, + public void putPermissionState(@NonNull PermissionState permissionState, @UserIdInt int userId) { checkUserId(userId); - putPermissionState(permissionState, userId); - } - - private void putPermissionState(@NonNull PermissionState permissionState, - @UserIdInt int userId) { UserState userState = mUserStates.get(userId); if (userState == null) { userState = new UserState(); @@ -157,29 +142,14 @@ public final class LegacyPermissionState { } /** - * Get all the install permission states. - * - * @return the install permission states - */ - @NonNull - public Collection<PermissionState> getInstallPermissionStates() { - return getPermissionStates(UserHandle.USER_ALL); - } - - /** * Get all the runtime permission states for a user. * * @param userId the user ID * @return the runtime permission states */ @NonNull - public Collection<PermissionState> getRuntimePermissionStates(@UserIdInt int userId) { + public Collection<PermissionState> getPermissionStates(@UserIdInt int userId) { checkUserId(userId); - return getPermissionStates(userId); - } - - @NonNull - private Collection<PermissionState> getPermissionStates(@UserIdInt int userId) { final UserState userState = mUserStates.get(userId); if (userState == null) { return Collections.emptyList(); @@ -265,6 +235,8 @@ public final class LegacyPermissionState { @NonNull private final String mName; + private final boolean mRuntime; + private final boolean mGranted; private final int mFlags; @@ -273,17 +245,20 @@ public final class LegacyPermissionState { * Create a new instance of this class. * * @param name the name of the permission + * @param runtime whether the permission is runtime * @param granted whether the permission is granted * @param flags the permission flags */ - public PermissionState(@NonNull String name, boolean granted, int flags) { + public PermissionState(@NonNull String name, boolean runtime, boolean granted, int flags) { mName = name; + mRuntime = runtime; mGranted = granted; mFlags = flags; } private PermissionState(@NonNull PermissionState other) { mName = other.mName; + mRuntime = other.mRuntime; mGranted = other.mGranted; mFlags = other.mFlags; } @@ -299,6 +274,15 @@ public final class LegacyPermissionState { } /** + * Get whether the permission is a runtime permission. + * + * @return whether the permission is a runtime permission. + */ + public boolean isRuntime() { + return mRuntime; + } + + /** * Get whether the permission is granted. * * @return whether the permission is granted 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 8f422890c973..2a646b557547 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -3965,8 +3965,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (!usedPermissions.contains(permissionState.getName())) { Permission bp = mRegistry.getPermission(permissionState.getName()); if (bp != null) { - if (uidState.removePermissionState(bp.getName()) - && permissionState.isRuntime()) { + if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) { runtimePermissionChanged = true; } } @@ -4573,9 +4572,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { uidState.reset(); uidState.setMissing(legacyState.isMissing(userId)); readLegacyPermissionStatesLocked(uidState, - legacyState.getInstallPermissionStates()); - readLegacyPermissionStatesLocked(uidState, - legacyState.getRuntimePermissionStates(userId)); + legacyState.getPermissionStates(userId)); } } }); @@ -4634,12 +4631,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { final LegacyPermissionState.PermissionState legacyPermissionState = new LegacyPermissionState.PermissionState(permissionState.getName(), + permissionState.getPermission().isRuntime(), permissionState.isGranted(), permissionState.getFlags()); - if (permissionState.isRuntime()) { - legacyState.putRuntimePermissionState(legacyPermissionState, userId); - } else { - legacyState.putInstallPermissionState(legacyPermissionState); - } + legacyState.putPermissionState(legacyPermissionState, userId); } } } @@ -4904,12 +4898,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { final LegacyPermissionState.PermissionState legacyPermissionState = new LegacyPermissionState.PermissionState(permissionState.getName(), + permissionState.getPermission().isRuntime(), permissionState.isGranted(), permissionState.getFlags()); - if (permissionState.isRuntime()) { - legacyState.putRuntimePermissionState(legacyPermissionState, userId); - } else if (userId == UserHandle.USER_SYSTEM) { - legacyState.putInstallPermissionState(legacyPermissionState); - } + legacyState.putPermissionState(legacyPermissionState, userId); } } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java index 12f29d091134..9ad8a05c945e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionState.java @@ -32,9 +32,6 @@ public final class PermissionState { private final Object mLock = new Object(); @GuardedBy("mLock") - private boolean mRuntime; - - @GuardedBy("mLock") private boolean mGranted; @GuardedBy("mLock") @@ -66,12 +63,6 @@ public final class PermissionState { return mPermission.computeGids(userId); } - public boolean isRuntime() { - synchronized (mLock) { - return mPermission.isRuntime(); - } - } - public boolean isGranted() { synchronized (mLock) { return mGranted; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 607c165c2543..69484b1e961d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -447,8 +447,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile int mPowerKeyPressCounter; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; - volatile boolean mGoingToSleep; - volatile boolean mRequestedOrGoingToSleep; + + /** + * {@code true} if the device is entering a low-power state; {@code false otherwise}. + * + * <p>This differs from {@link #mRequestedOrSleepingDefaultDisplay} which tracks the power state + * of the {@link #mDefaultDisplay default display} versus the power state of the entire device. + */ + volatile boolean mDeviceGoingToSleep; + + /** + * {@code true} if the {@link #mDefaultDisplay default display} is entering or was requested to + * enter a low-power state; {@code false otherwise}. + * + * <p>This differs from {@link #mDeviceGoingToSleep} which tracks the power state of the entire + * device versus the power state of the {@link #mDefaultDisplay default display}. + */ + // TODO(b/178103325): Track sleep/requested sleep for every display. + volatile boolean mRequestedOrSleepingDefaultDisplay; + volatile boolean mRecentsVisible; volatile boolean mNavBarVirtualKeyHapticFeedbackEnabled = true; volatile boolean mPictureInPictureVisible; @@ -916,7 +933,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (gestureService != null) { gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, mTmpBoolean); - if (mTmpBoolean.value && mRequestedOrGoingToSleep) { + if (mTmpBoolean.value && mRequestedOrSleepingDefaultDisplay) { mCameraGestureTriggeredDuringGoingToSleep = true; } } @@ -1063,13 +1080,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { case SHORT_PRESS_POWER_NOTHING: break; case SHORT_PRESS_POWER_GO_TO_SLEEP: - goToSleepFromPowerButton(eventTime, 0); + sleepDefaultDisplayFromPowerButton(eventTime, 0); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: - goToSleepFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + sleepDefaultDisplayFromPowerButton(eventTime, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: - if (goToSleepFromPowerButton(eventTime, + if (sleepDefaultDisplayFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) { launchHomeFromHotKey(DEFAULT_DISPLAY); } @@ -1097,11 +1115,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * Sends the device to sleep as a result of a power button press. + * Sends the default display to sleep as a result of a power button press. * - * @return True if the was device was sent to sleep, false if sleep was suppressed. + * @return True if the device was sent to sleep, false if the device did not sleep. */ - private boolean goToSleepFromPowerButton(long eventTime, int flags) { + private boolean sleepDefaultDisplayFromPowerButton(long eventTime, int flags) { // Before we actually go to sleep, we check the last wakeup reason. // If the device very recently woke up from a gesture (like user lifting their device) // then ignore the sleep instruction. This is because users have developed @@ -1121,12 +1139,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags); return true; } - private void goToSleep(long eventTime, int reason, int flags) { - mRequestedOrGoingToSleep = true; + private void sleepDefaultDisplay(long eventTime, int reason, int flags) { + mRequestedOrSleepingDefaultDisplay = true; mPowerManager.goToSleep(eventTime, reason, flags); } @@ -1163,7 +1181,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Global.THEATER_MODE_ON, 1); if (mGoToSleepOnButtonPressTheaterMode && interactive) { - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + 0); } } break; @@ -1271,7 +1290,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case SHORT_PRESS_SLEEP_GO_TO_SLEEP: case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME: Slog.i(TAG, "sleepRelease() calling goToSleep(GO_TO_SLEEP_REASON_SLEEP_BUTTON)"); - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); break; } } @@ -1897,8 +1916,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Match current screen state. if (!mPowerManager.isInteractive()) { - startedGoingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER); - finishedGoingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER); + startedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + finishedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); } mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @@ -3686,7 +3705,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if ((mEndcallBehavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) { - goToSleep(event.getEventTime(), + sleepDefaultDisplay(event.getEventTime(), PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); isWakeKey = false; } @@ -3729,10 +3748,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Any activity on the power button stops the accessibility shortcut result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately + final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState()); + final boolean interactiveAndOn = interactive && isDefaultDisplayOn; if (down) { - interceptPowerKeyDown(event, interactive); + interceptPowerKeyDown(event, interactiveAndOn); } else { - interceptPowerKeyUp(event, interactive, canceled); + interceptPowerKeyUp(event, interactiveAndOn, canceled); } break; } @@ -4234,32 +4255,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Called on the PowerManager's Notifier thread. @Override - public void startedGoingToSleep(int why) { + public void startedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { if (DEBUG_WAKEUP) { Slog.i(TAG, "Started going to sleep... (why=" - + WindowManagerPolicyConstants.offReasonToString(why) + ")"); + + WindowManagerPolicyConstants.offReasonToString( + WindowManagerPolicyConstants.translateSleepReasonToOffReason( + pmSleepReason)) + ")"); } - mGoingToSleep = true; - mRequestedOrGoingToSleep = true; + mDeviceGoingToSleep = true; if (mKeyguardDelegate != null) { - mKeyguardDelegate.onStartedGoingToSleep(why); + mKeyguardDelegate.onStartedGoingToSleep(pmSleepReason); } } // Called on the PowerManager's Notifier thread. @Override - public void finishedGoingToSleep(int why) { + public void finishedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { EventLogTags.writeScreenToggled(0); if (DEBUG_WAKEUP) { Slog.i(TAG, "Finished going to sleep... (why=" - + WindowManagerPolicyConstants.offReasonToString(why) + ")"); + + WindowManagerPolicyConstants.offReasonToString( + WindowManagerPolicyConstants.translateSleepReasonToOffReason( + pmSleepReason)) + ")"); } MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000); - mGoingToSleep = false; - mRequestedOrGoingToSleep = false; + mDeviceGoingToSleep = false; mDefaultDisplayPolicy.setAwake(false); // We must get this work done here because the power manager will drop @@ -4271,7 +4294,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onFinishedGoingToSleep(why, + mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, mCameraGestureTriggeredDuringGoingToSleep); } if (mDisplayFoldController != null) { @@ -4282,11 +4305,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Called on the PowerManager's Notifier thread. @Override - public void startedWakingUp(@OnReason int why) { + public void startedWakingUp(@PowerManager.WakeReason int pmWakeReason) { EventLogTags.writeScreenToggled(1); if (DEBUG_WAKEUP) { Slog.i(TAG, "Started waking up... (why=" - + WindowManagerPolicyConstants.onReasonToString(why) + ")"); + + WindowManagerPolicyConstants.onReasonToString( + WindowManagerPolicyConstants.translateWakeReasonToOnReason( + pmWakeReason)) + ")"); } mDefaultDisplayPolicy.setAwake(true); @@ -4302,16 +4327,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onStartedWakingUp(); + mKeyguardDelegate.onStartedWakingUp(pmWakeReason); } } // Called on the PowerManager's Notifier thread. @Override - public void finishedWakingUp(@OnReason int why) { + public void finishedWakingUp(@PowerManager.WakeReason int pmWakeReason) { if (DEBUG_WAKEUP) { Slog.i(TAG, "Finished waking up... (why=" - + WindowManagerPolicyConstants.onReasonToString(why) + ")"); + + WindowManagerPolicyConstants.onReasonToString( + WindowManagerPolicyConstants.translateWakeReasonToOnReason( + pmWakeReason)) + ")"); } if (mKeyguardDelegate != null) { @@ -4405,6 +4432,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off..."); + mRequestedOrSleepingDefaultDisplay = false; updateScreenOffSleepToken(true); mDefaultDisplayPolicy.screenTurnedOff(); synchronized (mLock) { @@ -4471,6 +4499,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return; } + mRequestedOrSleepingDefaultDisplay = true; mWindowManagerFuncs.screenTurningOff(screenOffListener); synchronized (mLock) { if (mKeyguardDelegate != null) { @@ -4556,7 +4585,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean okToAnimate() { - return mDefaultDisplayPolicy.isAwake() && !mGoingToSleep; + return mDefaultDisplayPolicy.isAwake() && !mDeviceGoingToSleep; } /** {@inheritDoc} */ @@ -4730,8 +4759,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onBootCompleted(); } } - startedWakingUp(ON_BECAUSE_OF_UNKNOWN); - finishedWakingUp(ON_BECAUSE_OF_UNKNOWN); + startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); + finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); screenTurningOn(DEFAULT_DISPLAY, null); screenTurnedOn(DEFAULT_DISPLAY); } @@ -4932,7 +4961,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerFuncs.lockDeviceNow(); break; case LID_BEHAVIOR_SLEEP: - goToSleep(SystemClock.uptimeMillis(), + sleepDefaultDisplay(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 1553966c9eca..e9d64406432a 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -73,6 +73,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerManager; import android.os.RemoteException; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -803,29 +804,35 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Called when the device has started waking up. + * + * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason we're + * waking up, such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. */ - void startedWakingUp(@OnReason int reason); + void startedWakingUp(@PowerManager.WakeReason int pmWakeReason); /** * Called when the device has finished waking up. + * + * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason we're + * waking up, such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. */ - void finishedWakingUp(@OnReason int reason); + void finishedWakingUp(@PowerManager.WakeReason int pmWakeReason); /** * Called when the device has started going to sleep. * - * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN}, - * or {@link #OFF_BECAUSE_OF_TIMEOUT}. + * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason + * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. */ - public void startedGoingToSleep(int why); + public void startedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason); /** * Called when the device has finished going to sleep. * - * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN}, - * or {@link #OFF_BECAUSE_OF_TIMEOUT}. + * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason + * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. */ - public void finishedGoingToSleep(int why); + public void finishedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason); /** * Called when the display is about to turn on to show content. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 9b67efe8f7ac..c2a1c7930b89 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -15,6 +15,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -176,7 +177,7 @@ public class KeyguardServiceDelegate { // This is used to hide the scrim once keyguard displays. if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) { - mKeyguardService.onStartedWakingUp(); + mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); } if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) { mKeyguardService.onFinishedWakingUp(); @@ -291,10 +292,10 @@ public class KeyguardServiceDelegate { mKeyguardState.dreaming = false; } - public void onStartedWakingUp() { + public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { if (mKeyguardService != null) { if (DEBUG) Log.v(TAG, "onStartedWakingUp()"); - mKeyguardService.onStartedWakingUp(); + mKeyguardService.onStartedWakingUp(pmWakeReason); } mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING; } @@ -345,17 +346,19 @@ public class KeyguardServiceDelegate { mKeyguardState.screenState = SCREEN_STATE_ON; } - public void onStartedGoingToSleep(int why) { + public void onStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { if (mKeyguardService != null) { - mKeyguardService.onStartedGoingToSleep(why); + mKeyguardService.onStartedGoingToSleep(pmSleepReason); } - mKeyguardState.offReason = why; + mKeyguardState.offReason = + WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason); mKeyguardState.interactiveState = INTERACTIVE_STATE_GOING_TO_SLEEP; } - public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) { + public void onFinishedGoingToSleep( + @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { if (mKeyguardService != null) { - mKeyguardService.onFinishedGoingToSleep(why, cameraGestureTriggered); + mKeyguardService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP; } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 4e848686254a..0872b3a5fd42 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -19,6 +19,7 @@ package com.android.server.policy.keyguard; import android.content.Context; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerManager; import android.os.RemoteException; import android.util.Slog; @@ -101,27 +102,28 @@ public class KeyguardServiceWrapper implements IKeyguardService { } @Override - public void onStartedGoingToSleep(int reason) { + public void onStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) { try { - mService.onStartedGoingToSleep(reason); + mService.onStartedGoingToSleep(pmSleepReason); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } } @Override - public void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered) { + public void onFinishedGoingToSleep( + @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { try { - mService.onFinishedGoingToSleep(reason, cameraGestureTriggered); + mService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } } @Override - public void onStartedWakingUp() { + public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) { try { - mService.onStartedWakingUp(); + mService.onStartedWakingUp(pmWakeReason); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java new file mode 100644 index 000000000000..8ebeea3be48f --- /dev/null +++ b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; +import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; +import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; +import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; + +import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_ADDED; +import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_CHANGED; +import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED; + +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.Handler; +import android.os.PowerManagerInternal; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; +import com.android.server.display.DisplayGroup; + +/** + * Responsible for creating {@link DisplayPowerRequest}s and associating them with + * {@link com.android.server.display.DisplayGroup}s. + * + * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest} + * which is used to request power state changes to every display in the group. + */ +public class DisplayGroupPowerStateMapper { + + private static final String TAG = "DisplayPowerRequestMapper"; + + /** Lock obtained from {@link PowerManagerService}. */ + private final Object mLock; + + /** Listener to inform of changes to display groups. */ + private final DisplayGroupPowerChangeListener mListener; + + /** A mapping from DisplayGroup Id to DisplayGroup information. */ + @GuardedBy("mLock") + private final SparseArray<DisplayGroupInfo> mDisplayGroupInfos = new SparseArray<>(); + + /** A cached array of DisplayGroup Ids. */ + @GuardedBy("mLock") + private int[] mDisplayGroupIds; + + private final DisplayManagerInternal.DisplayGroupListener mDisplayGroupListener = + new DisplayManagerInternal.DisplayGroupListener() { + @Override + public void onDisplayGroupAdded(int groupId) { + synchronized (mLock) { + if (mDisplayGroupInfos.contains(groupId)) { + Slog.e(TAG, "Tried to add already existing group:" + groupId); + return; + } + // For now, only the default group supports sandman. + final boolean supportsSandman = groupId == DisplayGroup.DEFAULT; + final DisplayGroupInfo displayGroupInfo = new DisplayGroupInfo( + new DisplayPowerRequest(), + getGlobalWakefulnessLocked(), /* ready= */ false, + supportsSandman); + mDisplayGroupInfos.append(groupId, displayGroupInfo); + mDisplayGroupIds = ArrayUtils.appendInt(mDisplayGroupIds, groupId); + mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_ADDED, groupId); + } + } + + @Override + public void onDisplayGroupRemoved(int groupId) { + synchronized (mLock) { + if (!mDisplayGroupInfos.contains(groupId)) { + Slog.e(TAG, "Tried to remove non-existent group:" + groupId); + return; + } + mDisplayGroupInfos.delete(groupId); + mDisplayGroupIds = ArrayUtils.removeInt(mDisplayGroupIds, groupId); + mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_REMOVED, groupId); + } + } + + @Override + public void onDisplayGroupChanged(int groupId) { + synchronized (mLock) { + mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_CHANGED, groupId); + } + } + }; + + DisplayGroupPowerStateMapper(Object lock, DisplayManagerInternal displayManagerInternal, + DisplayGroupPowerChangeListener listener) { + mLock = lock; + mListener = listener; + displayManagerInternal.registerDisplayGroupListener(mDisplayGroupListener); + + final DisplayGroupInfo displayGroupInfo = new DisplayGroupInfo( + new DisplayPowerRequest(), WAKEFULNESS_AWAKE, /* ready= */ + false, /* supportsSandman= */ true); + mDisplayGroupInfos.append(DisplayGroup.DEFAULT, displayGroupInfo); + mDisplayGroupIds = new int[]{DisplayGroup.DEFAULT}; + } + + DisplayPowerRequest getPowerRequestLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).displayPowerRequest; + } + + int[] getDisplayGroupIdsLocked() { + return mDisplayGroupIds; + } + + int getWakefulnessLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).wakefulness; + } + + void setLastPowerOnTimeLocked(int groupId, long eventTime) { + mDisplayGroupInfos.get(groupId).lastPowerOnTime = eventTime; + } + + long getLastPowerOnTimeLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).lastPowerOnTime; + } + + /** + * Returns the amalgamated wakefulness of all {@link DisplayGroup DisplayGroups}. + * + * <p>This will be the highest wakeful state of all {@link DisplayGroup DisplayGroups}; ordered + * from highest to lowest: + * <ol> + * <li>{@link PowerManagerInternal#WAKEFULNESS_AWAKE} + * <li>{@link PowerManagerInternal#WAKEFULNESS_DREAMING} + * <li>{@link PowerManagerInternal#WAKEFULNESS_DOZING} + * <li>{@link PowerManagerInternal#WAKEFULNESS_ASLEEP} + * </ol> + */ + int getGlobalWakefulnessLocked() { + final int size = mDisplayGroupInfos.size(); + int deviceWakefulness = WAKEFULNESS_ASLEEP; + for (int i = 0; i < size; i++) { + final int wakefulness = mDisplayGroupInfos.valueAt(i).wakefulness; + if (wakefulness == WAKEFULNESS_AWAKE) { + return WAKEFULNESS_AWAKE; + } else if (wakefulness == WAKEFULNESS_DREAMING + && (deviceWakefulness == WAKEFULNESS_ASLEEP + || deviceWakefulness == WAKEFULNESS_DOZING)) { + deviceWakefulness = WAKEFULNESS_DREAMING; + } else if (wakefulness == WAKEFULNESS_DOZING + && deviceWakefulness == WAKEFULNESS_ASLEEP) { + deviceWakefulness = WAKEFULNESS_DOZING; + } + } + + return deviceWakefulness; + } + + /** + * Sets the {@code wakefulness} value for the {@link DisplayGroup} specified by the provided + * {@code groupId}. + * + * @return {@code true} if the wakefulness value was changed; {@code false} otherwise. + */ + boolean setWakefulnessLocked(int groupId, int wakefulness) { + final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId); + if (displayGroupInfo.wakefulness != wakefulness) { + displayGroupInfo.wakefulness = wakefulness; + return true; + } + + return false; + } + + boolean isSandmanSummoned(int groupId) { + return mDisplayGroupInfos.get(groupId).sandmanSummoned; + } + + boolean isSandmanSupported(int groupId) { + return mDisplayGroupInfos.get(groupId).supportsSandman; + } + + /** + * Sets whether or not the sandman is summoned for the given {@code groupId}. + * + * @param groupId Signifies the DisplayGroup for which to summon or unsummon the + * sandman. + * @param sandmanSummoned {@code true} to summon the sandman; {@code false} to unsummon. + */ + void setSandmanSummoned(int groupId, boolean sandmanSummoned) { + final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId); + displayGroupInfo.sandmanSummoned = displayGroupInfo.supportsSandman && sandmanSummoned; + } + + /** + * Returns {@code true} if every display in the specified group has its requested state matching + * its actual state. + * + * @param groupId The identifier for the display group to check for readiness. + */ + boolean isReady(int groupId) { + return mDisplayGroupInfos.get(groupId).ready; + } + + /** Returns {@code true} if every display has its requested state matching its actual state. */ + boolean areAllDisplaysReadyLocked() { + final int size = mDisplayGroupInfos.size(); + for (int i = 0; i < size; i++) { + if (!mDisplayGroupInfos.valueAt(i).ready) { + return false; + } + } + + return true; + } + + /** + * Sets whether the displays specified by the provided {@code groupId} are all ready. + * + * <p>A display is ready if its reported + * {@link DisplayManagerInternal.DisplayPowerCallbacks#onStateChanged() actual state} matches + * its {@link DisplayManagerInternal#requestPowerState requested state}. + * + * @param groupId The identifier for the display group. + * @param ready {@code true} if every display in the group is ready; otherwise {@code false}. + * @return {@code true} if the ready state changed; otherwise {@code false}. + */ + boolean setDisplayGroupReadyLocked(int groupId, boolean ready) { + final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId); + if (displayGroupInfo.ready != ready) { + displayGroupInfo.ready = ready; + return true; + } + + return false; + } + + /** + * Interface through which an interested party may be informed of {@link DisplayGroup} events. + */ + interface DisplayGroupPowerChangeListener { + int DISPLAY_GROUP_ADDED = 0; + int DISPLAY_GROUP_REMOVED = 1; + int DISPLAY_GROUP_CHANGED = 2; + + void onDisplayGroupEventLocked(int event, int groupId); + } + + private static final class DisplayGroupInfo { + final DisplayPowerRequest displayPowerRequest; + int wakefulness; + boolean ready; + long lastPowerOnTime; + boolean sandmanSummoned; + + /** {@code true} if this DisplayGroup supports dreaming; otherwise {@code false}. */ + boolean supportsSandman; + + DisplayGroupInfo(DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready, + boolean supportsSandman) { + this.displayPowerRequest = displayPowerRequest; + this.wakefulness = wakefulness; + this.ready = ready; + this.supportsSandman = supportsSandman; + } + } +} diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java deleted file mode 100644 index 6477552eb550..000000000000 --- a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power; - -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; -import android.os.Handler; -import android.util.SparseArray; -import android.util.SparseIntArray; -import android.view.Display; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.display.DisplayGroup; - -/** - * Responsible for creating {@link DisplayPowerRequest}s and associating them with - * {@link com.android.server.display.DisplayGroup}s. - * - * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest} - * which is used to request power state changes to every display in the group. - */ -class DisplayPowerRequestMapper { - - private final Object mLock = new Object(); - - /** A mapping from LogicalDisplay Id to DisplayGroup Id. */ - @GuardedBy("mLock") - private final SparseIntArray mDisplayGroupIds = new SparseIntArray(); - - /** A mapping from DisplayGroup Id to DisplayPowerRequest. */ - @GuardedBy("mLock") - private final SparseArray<DisplayPowerRequest> mDisplayPowerRequests = new SparseArray<>(); - - private final DisplayManagerInternal mDisplayManagerInternal; - - private final DisplayManager.DisplayListener mDisplayListener = - new DisplayManager.DisplayListener() { - - @Override - public void onDisplayAdded(int displayId) { - synchronized (mLock) { - if (mDisplayGroupIds.indexOfKey(displayId) >= 0) { - return; - } - final int displayGroupId = mDisplayManagerInternal.getDisplayGroupId( - displayId); - if (!mDisplayPowerRequests.contains(displayGroupId)) { - // A new DisplayGroup was created; create a new DisplayPowerRequest. - mDisplayPowerRequests.append(displayGroupId, new DisplayPowerRequest()); - } - mDisplayGroupIds.append(displayId, displayGroupId); - } - } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - final int index = mDisplayGroupIds.indexOfKey(displayId); - if (index < 0) { - return; - } - final int displayGroupId = mDisplayGroupIds.valueAt(index); - mDisplayGroupIds.removeAt(index); - - if (mDisplayGroupIds.indexOfValue(displayGroupId) < 0) { - // The DisplayGroup no longer exists; delete the DisplayPowerRequest. - mDisplayPowerRequests.delete(displayGroupId); - } - } - } - - @Override - public void onDisplayChanged(int displayId) { - synchronized (mLock) { - final int newDisplayGroupId = mDisplayManagerInternal.getDisplayGroupId( - displayId); - final int oldDisplayGroupId = mDisplayGroupIds.get(displayId); - - if (!mDisplayPowerRequests.contains(newDisplayGroupId)) { - // A new DisplayGroup was created; create a new DisplayPowerRequest. - mDisplayPowerRequests.append(newDisplayGroupId, - new DisplayPowerRequest()); - } - mDisplayGroupIds.put(displayId, newDisplayGroupId); - - if (mDisplayGroupIds.indexOfValue(oldDisplayGroupId) < 0) { - // The DisplayGroup no longer exists; delete the DisplayPowerRequest. - mDisplayPowerRequests.delete(oldDisplayGroupId); - } - } - } - }; - - DisplayPowerRequestMapper(DisplayManager displayManager, - DisplayManagerInternal displayManagerInternal, Handler handler) { - mDisplayManagerInternal = displayManagerInternal; - displayManager.registerDisplayListener(mDisplayListener, handler); - mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest()); - mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT); - } - - DisplayPowerRequest get(int displayId) { - synchronized (mLock) { - return mDisplayPowerRequests.get(mDisplayGroupIds.get(displayId)); - } - } -} diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index d4375eb2935b..b8e0156fb4cc 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -36,7 +36,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.PowerManager.WakeReason; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; @@ -49,7 +48,7 @@ import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Slog; -import android.view.WindowManagerPolicyConstants.OnReason; +import android.view.WindowManagerPolicyConstants; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; @@ -455,13 +454,7 @@ public class Notifier { synchronized (mLock) { if (mInteractive) { // Waking up... - mHandler.post(new Runnable() { - @Override - public void run() { - final int why = translateOnReason(mInteractiveChangeReason); - mPolicy.startedWakingUp(why); - } - }); + mHandler.post(() -> mPolicy.startedWakingUp(mInteractiveChangeReason)); // Send interactive broadcast. mPendingInteractiveState = INTERACTIVE_STATE_AWAKE; @@ -470,13 +463,7 @@ public class Notifier { } else { // Going to sleep... // Tell the policy that we started going to sleep. - final int why = translateOffReason(mInteractiveChangeReason); - mHandler.post(new Runnable() { - @Override - public void run() { - mPolicy.startedGoingToSleep(why); - } - }); + mHandler.post(() -> mPolicy.startedGoingToSleep(mInteractiveChangeReason)); } } } @@ -492,20 +479,17 @@ public class Notifier { (int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime); if (mInteractive) { // Finished waking up... - final int why = translateOnReason(mInteractiveChangeReason); - mHandler.post(new Runnable() { - @Override - public void run() { - LogMaker log = new LogMaker(MetricsEvent.SCREEN); - log.setType(MetricsEvent.TYPE_OPEN); - log.setSubtype(why); - log.setLatency(interactiveChangeLatency); - log.addTaggedData( - MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason); - MetricsLogger.action(log); - EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency); - mPolicy.finishedWakingUp(why); - } + mHandler.post(() -> { + LogMaker log = new LogMaker(MetricsEvent.SCREEN); + log.setType(MetricsEvent.TYPE_OPEN); + log.setSubtype(WindowManagerPolicyConstants.translateWakeReasonToOnReason( + mInteractiveChangeReason)); + log.setLatency(interactiveChangeLatency); + log.addTaggedData( + MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason); + MetricsLogger.action(log); + EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency); + mPolicy.finishedWakingUp(mInteractiveChangeReason); }); } else { // Finished going to sleep... @@ -521,20 +505,19 @@ public class Notifier { } // Tell the policy we finished going to sleep. - final int why = translateOffReason(mInteractiveChangeReason); - mHandler.post(new Runnable() { - @Override - public void run() { - LogMaker log = new LogMaker(MetricsEvent.SCREEN); - log.setType(MetricsEvent.TYPE_CLOSE); - log.setSubtype(why); - log.setLatency(interactiveChangeLatency); - log.addTaggedData( - MetricsEvent.FIELD_SCREEN_SLEEP_REASON, mInteractiveChangeReason); - MetricsLogger.action(log); - EventLogTags.writePowerScreenState(0, why, 0, 0, interactiveChangeLatency); - mPolicy.finishedGoingToSleep(why); - } + final int offReason = WindowManagerPolicyConstants.translateSleepReasonToOffReason( + mInteractiveChangeReason); + mHandler.post(() -> { + LogMaker log = new LogMaker(MetricsEvent.SCREEN); + log.setType(MetricsEvent.TYPE_CLOSE); + log.setSubtype(offReason); + log.setLatency(interactiveChangeLatency); + log.addTaggedData( + MetricsEvent.FIELD_SCREEN_SLEEP_REASON, mInteractiveChangeReason); + MetricsLogger.action(log); + EventLogTags.writePowerScreenState( + 0, offReason, 0, 0, interactiveChangeLatency); + mPolicy.finishedGoingToSleep(mInteractiveChangeReason); }); // Send non-interactive broadcast. @@ -545,35 +528,6 @@ public class Notifier { } } - private static int translateOffReason(int reason) { - switch (reason) { - case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: - return WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN; - case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: - case PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE: - return WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT; - default: - return WindowManagerPolicy.OFF_BECAUSE_OF_USER; - } - } - - private static @OnReason int translateOnReason(@WakeReason int reason) { - switch (reason) { - case PowerManager.WAKE_REASON_POWER_BUTTON: - case PowerManager.WAKE_REASON_PLUGGED_IN: - case PowerManager.WAKE_REASON_GESTURE: - case PowerManager.WAKE_REASON_CAMERA_LAUNCH: - case PowerManager.WAKE_REASON_WAKE_KEY: - case PowerManager.WAKE_REASON_WAKE_MOTION: - case PowerManager.WAKE_REASON_LID: - return WindowManagerPolicy.ON_BECAUSE_OF_USER; - case PowerManager.WAKE_REASON_APPLICATION: - return WindowManagerPolicy.ON_BECAUSE_OF_APPLICATION; - default: - return WindowManagerPolicy.ON_BECAUSE_OF_UNKNOWN; - } - } - /** * Called when there has been user activity. */ diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 084dc32e8ad7..f14ff3cc3268 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -18,6 +18,10 @@ package com.android.server.power; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; +import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED; +import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF; +import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED; +import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_TURNED_ON; import static android.os.PowerManagerInternal.MODE_DEVICE_IDLE; import static android.os.PowerManagerInternal.MODE_DISPLAY_INACTIVE; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; @@ -42,7 +46,6 @@ import android.database.ContentObserver; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; import android.hardware.display.AmbientDisplayConfiguration; -import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.hardware.power.Boost; @@ -107,6 +110,7 @@ import com.android.server.UiThread; import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; +import com.android.server.display.DisplayGroup; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; @@ -176,6 +180,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_VR_MODE_CHANGED = 1 << 13; // Dirty bit: attentive timer may have timed out private static final int DIRTY_ATTENTIVE = 1 << 14; + // Dirty bit: display group power state has changed + private static final int DIRTY_DISPLAY_GROUP_POWER_UPDATED = 1 << 15; // Summarizes the state of all active wakelocks. private static final int WAKE_LOCK_CPU = 1 << 0; @@ -297,10 +303,6 @@ public final class PowerManagerService extends SystemService private int mWakefulnessRaw; private boolean mWakefulnessChanging; - // True if the sandman has just been summoned for the first time since entering the - // dreaming or dozing state. Indicates whether a new dream should begin. - private boolean mSandmanSummoned; - // True if MSG_SANDMAN has been scheduled. private boolean mSandmanScheduled; @@ -351,11 +353,7 @@ public final class PowerManagerService extends SystemService // Manages the desired power state of displays. The actual state may lag behind the // requested because it is updated asynchronously by the display power controller. - private DisplayPowerRequestMapper mDisplayPowerRequestMapper; - - // True if the display power state has been fully applied, which means the display - // is actually on or actually off or whatever was requested. - private boolean mDisplayReady; + private DisplayGroupPowerStateMapper mDisplayGroupPowerStateMapper; // The suspend blocker used to keep the CPU alive when an application has acquired // a wake lock. @@ -625,6 +623,39 @@ public final class PowerManagerService extends SystemService // but the DreamService has not yet been told to start (it's an async process). private boolean mDozeStartInProgress; + private final class DisplayGroupPowerChangeListener implements + DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener { + @Override + public void onDisplayGroupEventLocked(int event, int groupId) { + final int oldWakefulness = getWakefulnessLocked(); + final int newWakefulness = mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(); + if (oldWakefulness != newWakefulness) { + final int reason; + switch (newWakefulness) { + case WAKEFULNESS_AWAKE: + reason = event == DISPLAY_GROUP_ADDED ? WAKE_REASON_DISPLAY_GROUP_ADDED + : WAKE_REASON_DISPLAY_GROUP_TURNED_ON; + break; + case WAKEFULNESS_DOZING: + reason = event == DISPLAY_GROUP_REMOVED + ? GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED + : GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF; + break; + default: + reason = 0; + } + + setGlobalWakefulnessLocked( + mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(), + mClock.uptimeMillis(), reason, Process.SYSTEM_UID, Process.SYSTEM_UID, + mContext.getOpPackageName(), "groupId: " + groupId); + } + + mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; + updatePowerStateLocked(); + } + } + private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver { @Override public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException { @@ -868,6 +899,12 @@ public final class PowerManagerService extends SystemService void invalidateIsInteractiveCaches() { PowerManager.invalidateIsInteractiveCaches(); } + + DisplayGroupPowerStateMapper createDisplayPowerRequestMapper(Object lock, + DisplayManagerInternal displayManagerInternal, + DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener listener) { + return new DisplayGroupPowerStateMapper(lock, displayManagerInternal, listener); + } } final Constants mConstants; @@ -1037,7 +1074,7 @@ public final class PowerManagerService extends SystemService now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); if (sQuiescent) { - goToSleepNoUpdateLocked(mClock.uptimeMillis(), + sleepDisplayGroupNoUpdateLocked(DisplayGroup.DEFAULT, mClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_QUIESCENT, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID); } @@ -1055,8 +1092,8 @@ public final class PowerManagerService extends SystemService mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); mAttentionDetector.systemReady(mContext); - mDisplayPowerRequestMapper = new DisplayPowerRequestMapper(mContext.getSystemService( - DisplayManager.class), mDisplayManagerInternal, mHandler); + mDisplayGroupPowerStateMapper = mInjector.createDisplayPowerRequestMapper(mLock, + mDisplayManagerInternal, new DisplayGroupPowerChangeListener()); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -1373,9 +1410,11 @@ public final class PowerManagerService extends SystemService opPackageName = wakeLock.mPackageName; opUid = wakeLock.mOwnerUid; } - wakeUpNoUpdateLocked(mClock.uptimeMillis(), - PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag, - opUid, opPackageName, opUid); + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + wakeDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag, + opUid, opPackageName, opUid); + } } } @@ -1664,74 +1703,69 @@ public final class PowerManagerService extends SystemService } } - private void wakeUpInternal(long eventTime, @WakeReason int reason, String details, int uid, - String opPackageName, int opUid) { + private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason, + String details, int uid, String opPackageName, int opUid) { synchronized (mLock) { - if (wakeUpNoUpdateLocked(eventTime, reason, details, uid, opPackageName, opUid)) { + if (wakeDisplayGroupNoUpdateLocked(groupId, eventTime, reason, details, uid, + opPackageName, opUid)) { updatePowerStateLocked(); } } } - private boolean wakeUpNoUpdateLocked(long eventTime, @WakeReason int reason, String details, - int reasonUid, String opPackageName, int opUid) { + private boolean wakeDisplayGroupNoUpdateLocked(int groupId, long eventTime, + @WakeReason int reason, String details, int uid, String opPackageName, int opUid) { if (DEBUG_SPEW) { - Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime + ", uid=" + reasonUid); + Slog.d(TAG, "wakeDisplayGroupNoUpdateLocked: eventTime=" + eventTime + +", groupId=" + groupId + ", uid=" + uid); } - if (eventTime < mLastSleepTime || getWakefulnessLocked() == WAKEFULNESS_AWAKE - || mForceSuspendActive || !mSystemReady) { + if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) { return false; } - Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0); + final int currentState = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); + if (currentState == WAKEFULNESS_AWAKE) { + return false; + } - Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp"); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOnDisplay"); try { - Slog.i(TAG, "Waking up from " - + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked()) - + " (uid=" + reasonUid + Slog.i(TAG, "Powering on display group from" + + PowerManagerInternal.wakefulnessToString(currentState) + + " (groupId=" + groupId + + ", uid=" + uid + ", reason=" + PowerManager.wakeReasonToString(reason) + ", details=" + details + ")..."); + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); - mLastWakeTime = eventTime; - mLastWakeReason = reason; - setWakefulnessLocked(WAKEFULNESS_AWAKE, reason, eventTime); - - mNotifier.onWakeUp(reason, details, reasonUid, opPackageName, opUid); - userActivityNoUpdateLocked( - eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid); - - if (sQuiescent) { - mDirty |= DIRTY_QUIESCENT; - } + setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid, + opPackageName, details); + mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime); + mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } + return true; } - private void goToSleepInternal(long eventTime, int reason, int flags, int uid) { + private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags, + int uid) { synchronized (mLock) { - if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) { + if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) { updatePowerStateLocked(); } } } - /** - * Puts the system in doze. - * - * This method is called goToSleep for historical reasons but actually attempts to DOZE, - * and only tucks itself in to SLEEP if requested with the flag - * {@link PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE}. - */ - @SuppressWarnings("deprecation") - private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) { + private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason, + int flags, int uid) { if (DEBUG_SPEW) { - Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime - + ", reason=" + reason + ", flags=" + flags + ", uid=" + uid); + Slog.d(TAG, "powerOffDisplayGroupNoUpdateLocked: eventTime=" + eventTime + + ", groupId=" + groupId + ", reason=" + reason + ", flags=" + flags + + ", uid=" + uid); } if (eventTime < mLastWakeTime @@ -1742,55 +1776,44 @@ public final class PowerManagerService extends SystemService return false; } - Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep"); + final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); + if (!PowerManagerInternal.isInteractive(wakefulness)) { + return false; + } + + Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOffDisplay"); try { reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX, Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN)); - Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason) - + " (uid " + uid + ")..."); - - mLastSleepTime = eventTime; - mLastSleepReason = reason; - mSandmanSummoned = true; - mDozeStartInProgress = true; - setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime); + Slog.i(TAG, "Powering off display group due to " + + PowerManager.sleepReasonToString(reason) + " (groupId= " + groupId + + ", uid= " + uid + ")..."); - // Report the number of wake locks that will be cleared by going to sleep. - int numWakeLocksCleared = 0; - final int numWakeLocks = mWakeLocks.size(); - for (int i = 0; i < numWakeLocks; i++) { - final WakeLock wakeLock = mWakeLocks.get(i); - switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { - case PowerManager.FULL_WAKE_LOCK: - case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: - case PowerManager.SCREEN_DIM_WAKE_LOCK: - numWakeLocksCleared += 1; - break; - } - } - EventLogTags.writePowerSleepRequested(numWakeLocksCleared); - - // Skip dozing if requested. + mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true); + setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason, + /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null); if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { - reallyGoToSleepNoUpdateLocked(eventTime, uid); + reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid); } + mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } - private void napInternal(long eventTime, int uid) { + private void dreamDisplayGroup(int groupId, long eventTime, int uid) { synchronized (mLock) { - if (napNoUpdateLocked(eventTime, uid)) { + if (dreamDisplayGroupNoUpdateLocked(groupId, eventTime, uid)) { updatePowerStateLocked(); } } } - private boolean napNoUpdateLocked(long eventTime, int uid) { + private boolean dreamDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) { if (DEBUG_SPEW) { - Slog.d(TAG, "napNoUpdateLocked: eventTime=" + eventTime + ", uid=" + uid); + Slog.d(TAG, "dreamDisplayGroupNoUpdateLocked: eventTime=" + eventTime + + ", uid=" + uid); } if (eventTime < mLastWakeTime || getWakefulnessLocked() != WAKEFULNESS_AWAKE @@ -1798,36 +1821,42 @@ public final class PowerManagerService extends SystemService return false; } - Trace.traceBegin(Trace.TRACE_TAG_POWER, "nap"); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "napDisplayGroup"); try { - Slog.i(TAG, "Nap time (uid " + uid +")..."); + Slog.i(TAG, "Napping display group (groupId=" + groupId + ", uid=" + uid + ")..."); + + mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true); + setWakefulnessLocked(groupId, WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */ + 0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null); + mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; - mSandmanSummoned = true; - setWakefulnessLocked(WAKEFULNESS_DREAMING, 0, eventTime); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } - // Done dozing, drop everything and go to sleep. - private boolean reallyGoToSleepNoUpdateLocked(long eventTime, int uid) { + private boolean reallySleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) { if (DEBUG_SPEW) { - Slog.d(TAG, "reallyGoToSleepNoUpdateLocked: eventTime=" + eventTime + Slog.d(TAG, "reallySleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime + ", uid=" + uid); } if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP - || !mBootCompleted || !mSystemReady) { + || !mBootCompleted || !mSystemReady + || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) + == WAKEFULNESS_ASLEEP) { return false; } - Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallyGoToSleep"); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallySleepDisplayGroup"); try { - Slog.i(TAG, "Sleeping (uid " + uid +")..."); + Slog.i(TAG, "Sleeping display group (groupId=" + groupId + ", uid=" + uid +")..."); - setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, - eventTime); + setWakefulnessLocked(groupId, WAKEFULNESS_ASLEEP, eventTime, uid, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, /* opUid= */ 0, + /* opPackageName= */ null, /* details= */ null); + mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -1835,8 +1864,62 @@ public final class PowerManagerService extends SystemService } @VisibleForTesting - void setWakefulnessLocked(int wakefulness, int reason, long eventTime) { - if (getWakefulnessLocked() != wakefulness) { + void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason, + int opUid, String opPackageName, String details) { + if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, wakefulness)) { + setGlobalWakefulnessLocked(mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(), + eventTime, reason, uid, opUid, opPackageName, details); + } + } + + private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid, + int opUid, String opPackageName, String details) { + if (getWakefulnessLocked() == wakefulness) { + return; + } + + // Phase 1: Handle pre-wakefulness change bookkeeping. + final String traceMethodName; + switch (wakefulness) { + case WAKEFULNESS_ASLEEP: + traceMethodName = "reallyGoToSleep"; + Slog.i(TAG, "Sleeping (uid " + uid + ")..."); + break; + + case WAKEFULNESS_AWAKE: + traceMethodName = "wakeUp"; + Slog.i(TAG, "Waking up from " + + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked()) + + " (uid=" + uid + + ", reason=" + PowerManager.wakeReasonToString(reason) + + ", details=" + details + + ")..."); + mLastWakeTime = eventTime; + mLastWakeReason = reason; + break; + + case WAKEFULNESS_DREAMING: + traceMethodName = "nap"; + Slog.i(TAG, "Nap time (uid " + uid + ")..."); + break; + + case WAKEFULNESS_DOZING: + traceMethodName = "goToSleep"; + Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason) + + " (uid " + uid + ")..."); + + mLastSleepTime = eventTime; + mLastSleepReason = reason; + mDozeStartInProgress = true; + break; + + default: + throw new IllegalArgumentException("Unexpected wakefulness: " + wakefulness); + } + + Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName); + try { + // Phase 2: Handle wakefulness change and bookkeeping. // Under lock, invalidate before set ensures caches won't return stale values. mInjector.invalidateIsInteractiveCaches(); mWakefulnessRaw = wakefulness; @@ -1850,6 +1933,37 @@ public final class PowerManagerService extends SystemService mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime); } mAttentionDetector.onWakefulnessChangeStarted(wakefulness); + + // Phase 3: Handle post-wakefulness change bookkeeping. + switch (wakefulness) { + case WAKEFULNESS_AWAKE: + mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid); + userActivityNoUpdateLocked( + eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); + if (sQuiescent) { + mDirty |= DIRTY_QUIESCENT; + } + break; + + case WAKEFULNESS_DOZING: + // Report the number of wake locks that will be cleared by going to sleep. + int numWakeLocksCleared = 0; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + numWakeLocksCleared += 1; + break; + } + } + EventLogTags.writePowerSleepRequested(numWakeLocksCleared); + break; + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); } } @@ -1872,7 +1986,7 @@ public final class PowerManagerService extends SystemService } private void finishWakefulnessChangeIfNeededLocked() { - if (mWakefulnessChanging && mDisplayReady) { + if (mWakefulnessChanging && mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { if (getWakefulnessLocked() == WAKEFULNESS_DOZING && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) { return; // wait until dream has enabled dozing @@ -1884,13 +1998,6 @@ public final class PowerManagerService extends SystemService || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) { logSleepTimeoutRecapturedLocked(); } - if (getWakefulnessLocked() == WAKEFULNESS_AWAKE) { - Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0); - final int latencyMs = (int) (mClock.uptimeMillis() - mLastWakeTime); - if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) { - Slog.w(TAG, "Screen on took " + latencyMs + " ms"); - } - } mWakefulnessChanging = false; mNotifier.onWakefulnessChangeFinished(); } @@ -1989,7 +2096,6 @@ public final class PowerManagerService extends SystemService if ((dirty & DIRTY_BATTERY_STATE) != 0) { final boolean wasPowered = mIsPowered; final int oldPlugType = mPlugType; - final boolean oldLevelLow = mBatteryLevelLow; mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); mPlugType = mBatteryManagerInternal.getPlugType(); mBatteryLevel = mBatteryManagerInternal.getBatteryLevel(); @@ -2018,7 +2124,8 @@ public final class PowerManagerService extends SystemService final long now = mClock.uptimeMillis(); if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType, dockedOnWirelessCharger)) { - wakeUpNoUpdateLocked(now, PowerManager.WAKE_REASON_PLUGGED_IN, + wakeDisplayGroupNoUpdateLocked(DisplayGroup.DEFAULT, now, + PowerManager.WAKE_REASON_PLUGGED_IN, "android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID, mContext.getOpPackageName(), Process.SYSTEM_UID); } @@ -2300,7 +2407,8 @@ public final class PowerManagerService extends SystemService nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; if (now < nextTimeout) { final DisplayPowerRequest displayPowerRequest = - mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY); + mDisplayGroupPowerStateMapper.getPowerRequestLocked( + DisplayGroup.DEFAULT); if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) { mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; @@ -2555,13 +2663,23 @@ public final class PowerManagerService extends SystemService } final long time = mClock.uptimeMillis(); if (isAttentiveTimeoutExpired(time)) { - changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, - PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID); + // TODO (b/175764389): Support per-display timeouts. + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + changed = sleepDisplayGroupNoUpdateLocked(id, time, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID); + } } else if (shouldNapAtBedTimeLocked()) { - changed = napNoUpdateLocked(time, Process.SYSTEM_UID); + // TODO (b/175764389): Support per-display timeouts. + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID); + } } else { - changed = goToSleepNoUpdateLocked(time, - PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); + // TODO (b/175764389): Support per-display timeouts. + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + changed = sleepDisplayGroupNoUpdateLocked(id, time, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); + } } } } @@ -2644,7 +2762,7 @@ public final class PowerManagerService extends SystemService | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_BATTERY_STATE)) != 0 || displayBecameReady) { - if (mDisplayReady) { + if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { scheduleSandmanLocked(); } } @@ -2659,6 +2777,14 @@ public final class PowerManagerService extends SystemService } } + private void handleSandman() { + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + if (mDisplayGroupPowerStateMapper.isSandmanSupported(id)) { + handleSandman(id); + } + } + } + /** * Called when the device enters or exits a dreaming or dozing state. * @@ -2666,16 +2792,18 @@ public final class PowerManagerService extends SystemService * the dream and we don't want to hold our lock while doing so. There is a risk that * the device will wake or go to sleep in the meantime so we have to handle that case. */ - private void handleSandman() { // runs on handler thread + private void handleSandman(int groupId) { // runs on handler thread // Handle preconditions. final boolean startDreaming; final int wakefulness; synchronized (mLock) { mSandmanScheduled = false; + // TODO (b/175764708): Support per-display doze. wakefulness = getWakefulnessLocked(); - if (mSandmanSummoned && mDisplayReady) { - startDreaming = canDreamLocked() || canDozeLocked(); - mSandmanSummoned = false; + if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId) + && mDisplayGroupPowerStateMapper.isReady(groupId)) { + startDreaming = canDreamLocked(groupId) || canDozeLocked(); + mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, false); } else { startDreaming = false; } @@ -2714,14 +2842,15 @@ public final class PowerManagerService extends SystemService // If preconditions changed, wait for the next iteration to determine // whether the dream should continue (or be restarted). - if (mSandmanSummoned || getWakefulnessLocked() != wakefulness) { + if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId) + || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != wakefulness) { return; // wait for next cycle } // Determine whether the dream should continue. long now = mClock.uptimeMillis(); if (wakefulness == WAKEFULNESS_DREAMING) { - if (isDreaming && canDreamLocked()) { + if (isDreaming && canDreamLocked(groupId)) { if (mDreamsBatteryLevelDrainCutoffConfig >= 0 && mBatteryLevel < mBatteryLevelWhenDreamStarted - mDreamsBatteryLevelDrainCutoffConfig @@ -2741,16 +2870,13 @@ public final class PowerManagerService extends SystemService // Dream has ended or will be stopped. Update the power state. if (isItBedTimeYetLocked()) { - int flags = 0; - if (isAttentiveTimeoutExpired(now)) { - flags |= PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE; - } - goToSleepNoUpdateLocked(now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, - Process.SYSTEM_UID); + final int flags = isAttentiveTimeoutExpired(now) + ? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0; + sleepDisplayGroupNoUpdateLocked(groupId, now, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID); updatePowerStateLocked(); } else { - wakeUpNoUpdateLocked(now, - PowerManager.WAKE_REASON_UNKNOWN, + wakeDisplayGroupNoUpdateLocked(groupId, now, PowerManager.WAKE_REASON_UNKNOWN, "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID, mContext.getOpPackageName(), Process.SYSTEM_UID); updatePowerStateLocked(); @@ -2761,7 +2887,7 @@ public final class PowerManagerService extends SystemService } // Doze has ended or will be stopped. Update the power state. - reallyGoToSleepNoUpdateLocked(now, Process.SYSTEM_UID); + reallySleepDisplayGroupNoUpdateLocked(groupId, now, Process.SYSTEM_UID); updatePowerStateLocked(); } } @@ -2773,11 +2899,11 @@ public final class PowerManagerService extends SystemService } /** - * Returns true if the device is allowed to dream in its current state. + * Returns true if the {@code groupId} is allowed to dream in its current state. */ - private boolean canDreamLocked() { + private boolean canDreamLocked(int groupId) { final DisplayPowerRequest displayPowerRequest = - mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY); + mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId); if (getWakefulnessLocked() != WAKEFULNESS_DREAMING || !mDreamsSupportedConfig || !mDreamsEnabledSetting @@ -2815,91 +2941,113 @@ public final class PowerManagerService extends SystemService /** * Updates the display power state asynchronously. - * When the update is finished, mDisplayReady will be set to true. The display - * controller posts a message to tell us when the actual display power state + * When the update is finished, the ready state of the displays will be updated. The display + * controllers post a message to tell us when the actual display power state * has been updated so we come back here to double-check and finish up. * * This function recalculates the display power state each time. * - * @return True if the display became ready. + * @return {@code true} if all displays became ready; {@code false} otherwise */ private boolean updateDisplayPowerStateLocked(int dirty) { - final boolean oldDisplayReady = mDisplayReady; + final boolean oldDisplayReady = mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked(); if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED | - DIRTY_QUIESCENT)) != 0) { + DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_POWER_UPDATED)) != 0) { if ((dirty & DIRTY_QUIESCENT) != 0) { sQuiescent = false; } - final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get( - Display.DEFAULT_DISPLAY); - displayPowerRequest.policy = getDesiredScreenPolicyLocked(); - - // Determine appropriate screen brightness and auto-brightness adjustments. - final boolean autoBrightness; - final float screenBrightnessOverride; - if (!mBootCompleted) { - // Keep the brightness steady during boot. This requires the - // bootloader brightness and the default brightness to be identical. - autoBrightness = false; - screenBrightnessOverride = mScreenBrightnessDefault; - } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { - autoBrightness = false; - screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager; - } else { - autoBrightness = (mScreenBrightnessModeSetting == - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; - } + for (final int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + final DisplayPowerRequest displayPowerRequest = + mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId); + displayPowerRequest.policy = getDesiredScreenPolicyLocked(groupId); + + // Determine appropriate screen brightness and auto-brightness adjustments. + final boolean autoBrightness; + final float screenBrightnessOverride; + if (!mBootCompleted) { + // Keep the brightness steady during boot. This requires the + // bootloader brightness and the default brightness to be identical. + autoBrightness = false; + screenBrightnessOverride = mScreenBrightnessDefault; + } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { + autoBrightness = false; + screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager; + } else { + autoBrightness = (mScreenBrightnessModeSetting == + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; + } - // Update display power request. - displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride; - displayPowerRequest.useAutoBrightness = autoBrightness; - displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); - displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness(); + // Update display power request. + displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride; + displayPowerRequest.useAutoBrightness = autoBrightness; + displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); + displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness(); - updatePowerRequestFromBatterySaverPolicy(displayPowerRequest); + updatePowerRequestFromBatterySaverPolicy(displayPowerRequest); - if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { - displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; - if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0 - && !mDrawWakeLockOverrideFromSidekick) { - if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) { - displayPowerRequest.dozeScreenState = Display.STATE_DOZE; + if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { + displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; + if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0 + && !mDrawWakeLockOverrideFromSidekick) { + if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) { + displayPowerRequest.dozeScreenState = Display.STATE_DOZE; + } + if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) { + displayPowerRequest.dozeScreenState = Display.STATE_ON; + } } - if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) { - displayPowerRequest.dozeScreenState = Display.STATE_ON; + displayPowerRequest.dozeScreenBrightness = + mDozeScreenBrightnessOverrideFromDreamManagerFloat; + } else { + displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN; + displayPowerRequest.dozeScreenBrightness = + PowerManager.BRIGHTNESS_INVALID_FLOAT; + } + + final boolean ready = mDisplayManagerInternal.requestPowerState(groupId, + displayPowerRequest, mRequestWaitForNegativeProximity); + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready + + ", groupId=" + groupId + + ", policy=" + displayPowerRequest.policy + + ", mWakefulness=" + + mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) + + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + + ", mUserActivitySummary=0x" + Integer.toHexString( + mUserActivitySummary) + + ", mBootCompleted=" + mBootCompleted + + ", screenBrightnessOverride=" + + displayPowerRequest.screenBrightnessOverride + + ", useAutoBrightness=" + displayPowerRequest.useAutoBrightness + + ", mScreenBrightnessBoostInProgress=" + + mScreenBrightnessBoostInProgress + + ", mIsVrModeEnabled= " + mIsVrModeEnabled + + ", sQuiescent=" + sQuiescent); + } + + final boolean displayReadyStateChanged = + mDisplayGroupPowerStateMapper.setDisplayGroupReadyLocked(groupId, ready); + if (ready && displayReadyStateChanged + && mDisplayGroupPowerStateMapper.getWakefulnessLocked( + groupId) == WAKEFULNESS_AWAKE) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); + final int latencyMs = (int) (mClock.uptimeMillis() + - mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId)); + if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) { + Slog.w(TAG, "Screen on took " + latencyMs + " ms"); } } - displayPowerRequest.dozeScreenBrightness = - mDozeScreenBrightnessOverrideFromDreamManagerFloat; - } else { - displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN; - displayPowerRequest.dozeScreenBrightness = - PowerManager.BRIGHTNESS_INVALID_FLOAT; } - mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest, - mRequestWaitForNegativeProximity); mRequestWaitForNegativeProximity = false; - if (DEBUG_SPEW) { - Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady - + ", policy=" + displayPowerRequest.policy - + ", mWakefulness=" + getWakefulnessLocked() - + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) - + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) - + ", mBootCompleted=" + mBootCompleted - + ", screenBrightnessOverride=" + screenBrightnessOverride - + ", useAutoBrightness=" + autoBrightness - + ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress - + ", mIsVrModeEnabled= " + mIsVrModeEnabled - + ", sQuiescent=" + sQuiescent); - } } - return mDisplayReady && !oldDisplayReady; + return mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked() && !oldDisplayReady; } private void updateScreenBrightnessBoostLocked(int dirty) { @@ -2933,12 +3081,11 @@ public final class PowerManagerService extends SystemService } @VisibleForTesting - int getDesiredScreenPolicyLocked() { - if (getWakefulnessLocked() == WAKEFULNESS_ASLEEP || sQuiescent) { + int getDesiredScreenPolicyLocked(int groupId) { + final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); + if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { return DisplayPowerRequest.POLICY_OFF; - } - - if (getWakefulnessLocked() == WAKEFULNESS_DOZING) { + } else if (wakefulness == WAKEFULNESS_DOZING) { if ((mWakeLockSummary & WAKE_LOCK_DOZE) != 0) { return DisplayPowerRequest.POLICY_DOZE; } @@ -2968,7 +3115,6 @@ public final class PowerManagerService extends SystemService private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks = new DisplayManagerInternal.DisplayPowerCallbacks() { - private int mDisplayState = Display.STATE_UNKNOWN; @Override public void onStateChanged() { @@ -2999,29 +3145,25 @@ public final class PowerManagerService extends SystemService } @Override - public void onDisplayStateChange(int state) { + public void onDisplayStateChange(boolean allInactive, boolean allOff) { // This method is only needed to support legacy display blanking behavior // where the display's power state is coupled to suspend or to the power HAL. // The order of operations matters here. synchronized (mLock) { - if (mDisplayState != state) { - mDisplayState = state; - setPowerModeInternal(MODE_DISPLAY_INACTIVE, - !Display.isActiveState(state)); - if (state == Display.STATE_OFF) { - if (!mDecoupleHalInteractiveModeFromDisplayConfig) { - setHalInteractiveModeLocked(false); - } - if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { - setHalAutoSuspendModeLocked(true); - } - } else { - if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { - setHalAutoSuspendModeLocked(false); - } - if (!mDecoupleHalInteractiveModeFromDisplayConfig) { - setHalInteractiveModeLocked(true); - } + setPowerModeInternal(MODE_DISPLAY_INACTIVE, allInactive); + if (allOff) { + if (!mDecoupleHalInteractiveModeFromDisplayConfig) { + setHalInteractiveModeLocked(false); + } + if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { + setHalAutoSuspendModeLocked(true); + } + } else { + if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) { + setHalAutoSuspendModeLocked(false); + } + if (!mDecoupleHalInteractiveModeFromDisplayConfig) { + setHalInteractiveModeLocked(true); } } } @@ -3036,13 +3178,6 @@ public final class PowerManagerService extends SystemService public void releaseSuspendBlocker() { mDisplaySuspendBlocker.release(); } - - @Override - public String toString() { - synchronized (this) { - return "state=" + Display.stateToString(mDisplayState); - } - } }; private boolean shouldUseProximitySensorLocked() { @@ -3058,9 +3193,13 @@ public final class PowerManagerService extends SystemService final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked(); final boolean autoSuspend = !needDisplaySuspendBlocker; - final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get( - Display.DEFAULT_DISPLAY); - final boolean interactive = displayPowerRequest.isBrightOrDim(); + final DisplayPowerRequest displayPowerRequest = + mDisplayGroupPowerStateMapper.getPowerRequestLocked(DisplayGroup.DEFAULT); + final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); + boolean interactive = false; + for (int id : groupIds) { + interactive |= mDisplayGroupPowerStateMapper.getPowerRequestLocked(id).isBrightOrDim(); + } // Disable auto-suspend if needed. // FIXME We should consider just leaving auto-suspend enabled forever since @@ -3090,7 +3229,7 @@ public final class PowerManagerService extends SystemService // until the display is actually ready so that all transitions have // completed. This is probably a good sign that things have gotten // too tangled over here... - if (interactive || mDisplayReady) { + if (interactive || mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { setHalInteractiveModeLocked(interactive); } } @@ -3116,29 +3255,10 @@ public final class PowerManagerService extends SystemService * We do so if the screen is on or is in transition between states. */ private boolean needDisplaySuspendBlockerLocked() { - if (!mDisplayReady) { + if (!mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { return true; } - final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get( - Display.DEFAULT_DISPLAY); - if (displayPowerRequest.isBrightOrDim()) { - // If we asked for the screen to be on but it is off due to the proximity - // sensor then we may suspend but only if the configuration allows it. - // On some hardware it may not be safe to suspend because the proximity - // sensor may not be correctly configured as a wake-up source. - if (!displayPowerRequest.useProximitySensor || !mProximityPositive - || !mSuspendWhenScreenOffDueToProximityConfig) { - return true; - } - } - if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE - && displayPowerRequest.dozeScreenState == Display.STATE_ON) { - // Although we are in DOZE and would normally allow the device to suspend, - // the doze service has explicitly requested the display to remain in the ON - // state which means we should hold the display suspend blocker. - return true; - } if (mScreenBrightnessBoostInProgress) { return true; } @@ -3152,6 +3272,30 @@ public final class PowerManagerService extends SystemService return true; } + final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); + for (int id : groupIds) { + final DisplayPowerRequest displayPowerRequest = + mDisplayGroupPowerStateMapper.getPowerRequestLocked(id); + if (displayPowerRequest.isBrightOrDim()) { + // If we asked for the screen to be on but it is off due to the proximity + // sensor then we may suspend but only if the configuration allows it. + // On some hardware it may not be safe to suspend because the proximity + // sensor may not be correctly configured as a wake-up source. + if (!displayPowerRequest.useProximitySensor || !mProximityPositive + || !mSuspendWhenScreenOffDueToProximityConfig) { + return true; + } + } + + if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE + && displayPowerRequest.dozeScreenState == Display.STATE_ON) { + // Although we are in DOZE and would normally allow the device to suspend, + // the doze service has explicitly requested the display to remain in the ON + // state which means we should hold the display suspend blocker. + return true; + } + } + // Let the system suspend if the screen is off or dozing. return false; } @@ -3685,9 +3829,15 @@ public final class PowerManagerService extends SystemService synchronized (mLock) { mForceSuspendActive = true; // Place the system in an non-interactive state - goToSleepInternal(mClock.uptimeMillis(), - PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND, - PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid); + boolean updatePowerState = false; + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + updatePowerState |= sleepDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid); + } + if (updatePowerState) { + updatePowerStateLocked(); + } // Disable all the partial wake locks as well updateWakeLockDisabledStatesLocked(); @@ -3827,7 +3977,6 @@ public final class PowerManagerService extends SystemService pw.println(" mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)); pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity); pw.println(" mSandmanScheduled=" + mSandmanScheduled); - pw.println(" mSandmanSummoned=" + mSandmanSummoned); pw.println(" mBatteryLevelLow=" + mBatteryLevelLow); pw.println(" mLightDeviceIdleMode=" + mLightDeviceIdleMode); pw.println(" mDeviceIdleMode=" + mDeviceIdleMode); @@ -3845,7 +3994,6 @@ public final class PowerManagerService extends SystemService + TimeUtils.formatUptime(mLastScreenBrightnessBoostTime)); pw.println(" mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress); - pw.println(" mDisplayReady=" + mDisplayReady); pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker); pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker); @@ -4080,7 +4228,6 @@ public final class PowerManagerService extends SystemService PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, mRequestWaitForNegativeProximity); proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled); - proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned); proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow); proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode); proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode); @@ -4107,7 +4254,6 @@ public final class PowerManagerService extends SystemService proto.write( PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS, mScreenBrightnessBoostInProgress); - proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady); proto.write( PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER, mHoldingWakeLockSuspendBlocker); @@ -4921,7 +5067,8 @@ public final class PowerManagerService extends SystemService final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - wakeUpInternal(eventTime, reason, details, uid, opPackageName, uid); + wakeDisplayGroup(DisplayGroup.DEFAULT, eventTime, reason, details, uid, + opPackageName, uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -4939,7 +5086,7 @@ public final class PowerManagerService extends SystemService final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - goToSleepInternal(eventTime, reason, flags, uid); + sleepDisplayGroup(DisplayGroup.DEFAULT, eventTime, reason, flags, uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -4957,7 +5104,7 @@ public final class PowerManagerService extends SystemService final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - napInternal(eventTime, uid); + dreamDisplayGroup(DisplayGroup.DEFAULT, eventTime, uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -5590,7 +5737,7 @@ public final class PowerManagerService extends SystemService // also tells us that we're not already ignoring the proximity sensor. final DisplayPowerRequest displayPowerRequest = - mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY); + mDisplayGroupPowerStateMapper.getPowerRequestLocked(DisplayGroup.DEFAULT); if (displayPowerRequest.useProximitySensor && mProximityPositive) { mDisplayManagerInternal.ignoreProximitySensorUntilChanged(); return true; diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java index c9e81ed7a796..2e4b054b829c 100644 --- a/services/core/java/com/android/server/power/PreRebootLogger.java +++ b/services/core/java/com/android/server/power/PreRebootLogger.java @@ -19,6 +19,7 @@ package com.android.server.power; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.content.Context; +import android.os.Binder; import android.os.Environment; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -146,6 +147,7 @@ final class PreRebootLogger { return; } + final long token = Binder.clearCallingIdentity(); try { final File dumpFile = new File(dumpDir, serviceName); final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile, @@ -154,6 +156,8 @@ final class PreRebootLogger { binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); } catch (FileNotFoundException | RemoteException e) { Slog.e(TAG, String.format("Failed to dump %s service before reboot", serviceName), e); + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index 5c01e43af5a9..fd2d8e1b834b 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.content.Context; import android.content.IntentSender; import android.content.pm.PackageManager; +import android.hardware.boot.V1_0.IBootControl; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Binder; @@ -73,6 +74,8 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo static final String INIT_SERVICE_SETUP_BCB = "init.svc.setup-bcb"; @VisibleForTesting static final String INIT_SERVICE_CLEAR_BCB = "init.svc.clear-bcb"; + @VisibleForTesting + static final String AB_UPDATE = "ro.build.ab_update"; private static final Object sRequestLock = new Object(); @@ -177,6 +180,25 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return socket; } + /** + * Throws remote exception if there's an error getting the boot control HAL. + * Returns null if the boot control HAL's version is older than V1_2. + */ + public android.hardware.boot.V1_2.IBootControl getBootControl() throws RemoteException { + IBootControl bootControlV10 = IBootControl.getService(true); + if (bootControlV10 == null) { + throw new RemoteException("Failed to get boot control HAL V1_0."); + } + + android.hardware.boot.V1_2.IBootControl bootControlV12 = + android.hardware.boot.V1_2.IBootControl.castFrom(bootControlV10); + if (bootControlV12 == null) { + Slog.w(TAG, "Device doesn't implement boot control HAL V1_2."); + return null; + } + return bootControlV12; + } + public void threadSleep(long millis) throws InterruptedException { Thread.sleep(millis); } @@ -476,6 +498,56 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return needClear ? ROR_REQUESTED_NEED_CLEAR : ROR_REQUESTED_SKIP_CLEAR; } + private boolean isAbDevice() { + return "true".equalsIgnoreCase(mInjector.systemPropertiesGet(AB_UPDATE)); + } + + private boolean verifySlotForNextBoot(boolean slotSwitch) { + if (!isAbDevice()) { + Slog.w(TAG, "Device isn't a/b, skipping slot verification."); + return true; + } + + android.hardware.boot.V1_2.IBootControl bootControl; + try { + bootControl = mInjector.getBootControl(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to get the boot control HAL " + e); + return false; + } + + // TODO(xunchang) enforce boot control V1_2 HAL on devices using multi client RoR + if (bootControl == null) { + Slog.w(TAG, "Cannot get the boot control HAL, skipping slot verification."); + return true; + } + + int current_slot; + int next_active_slot; + try { + current_slot = bootControl.getCurrentSlot(); + if (current_slot != 0 && current_slot != 1) { + throw new IllegalStateException("Current boot slot should be 0 or 1, got " + + current_slot); + } + next_active_slot = bootControl.getActiveBootSlot(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to query the active slots", e); + return false; + } + + int expected_active_slot = current_slot; + if (slotSwitch) { + expected_active_slot = current_slot == 0 ? 1 : 0; + } + if (next_active_slot != expected_active_slot) { + Slog.w(TAG, "The next active boot slot doesn't match the expected value, " + + "expected " + expected_active_slot + ", got " + next_active_slot); + return false; + } + return true; + } + private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) { if (packageName == null) { Slog.w(TAG, "Missing packageName when rebooting with lskf."); @@ -485,7 +557,10 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return false; } - // TODO(xunchang) check the slot to boot into, and fail the reboot upon slot mismatch. + if (!verifySlotForNextBoot(slotSwitch)) { + return false; + } + // TODO(xunchang) write the vbmeta digest along with the escrowKey before reboot. if (!mInjector.getLockSettingsService().armRebootEscrow()) { Slog.w(TAG, "Failure to escrow key for reboot"); diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java index f20d80d57476..ae71c1a1e444 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java @@ -76,7 +76,7 @@ public class RecoverySystemShellCommand extends ShellCommand { private int rebootAndApply() throws RemoteException { String packageName = getNextArgRequired(); String rebootReason = getNextArgRequired(); - boolean success = mService.rebootWithLskf(packageName, rebootReason, true); + boolean success = mService.rebootWithLskf(packageName, rebootReason, false); PrintWriter pw = getOutPrintWriter(); // Keep the old message for cts test. pw.printf("%s Reboot and apply status: %s\n", packageName, diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 225bd82e9a21..6ec71b717ec6 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -60,8 +60,7 @@ public class FileIntegrityService extends SystemService { private final IBinder mService = new IFileIntegrityService.Stub() { @Override public boolean isApkVeritySupported() { - return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R - || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; + return FileIntegrityService.isApkVeritySupported(); } @Override @@ -111,6 +110,11 @@ public class FileIntegrityService extends SystemService { } }; + public static boolean isApkVeritySupported() { + return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R + || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; + } + public FileIntegrityService(final Context context) { super(context); try { diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS new file mode 100644 index 000000000000..91b240bcb189 --- /dev/null +++ b/services/core/java/com/android/server/security/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 36824 + +per-file FileIntegrityService.java = victorhsieh@google.com +per-file VerityUtils.java = victorhsieh@google.com diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index f204aa2cc1ca..09ee001a5544 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -73,7 +73,12 @@ abstract public class VerityUtils { if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } - byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); + setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath))); + } + + /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */ + public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature) + throws IOException { // This will fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { diff --git a/services/core/java/com/android/server/textservices/OWNERS b/services/core/java/com/android/server/textservices/OWNERS new file mode 100644 index 000000000000..9fa9b296706d --- /dev/null +++ b/services/core/java/com/android/server/textservices/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 816455 + +include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java index f39067b110a8..cd2b8943ce11 100644 --- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java +++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java @@ -45,6 +45,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSubtype; +import android.view.textservice.SuggestionsInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; @@ -58,7 +59,6 @@ import com.android.internal.textservice.ITextServicesSessionListener; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParserException; @@ -567,7 +567,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { @Override public void getSpellCheckerService(@UserIdInt int userId, String sciId, String locale, ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, - Bundle bundle) { + Bundle bundle, @SuggestionsInfo.ResultAttrs int supportedAttributes) { verifyUser(userId); if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) { Slog.e(TAG, "getSpellCheckerService: Invalid input."); @@ -603,7 +603,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { // Start getISpellCheckerSession async IPC, or just queue the request until the spell // checker service is bound. bindGroup.getISpellCheckerSessionOrQueueLocked( - new SessionRequest(uid, locale, tsListener, scListener, bundle)); + new SessionRequest(uid, locale, tsListener, scListener, bundle, + supportedAttributes)); } } @@ -774,15 +775,18 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { public final ISpellCheckerSessionListener mScListener; @Nullable public final Bundle mBundle; + public final int mSupportedAttributes; SessionRequest(int uid, @Nullable String locale, @NonNull ITextServicesSessionListener tsListener, - @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle) { + @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle, + @SuggestionsInfo.ResultAttrs int supportedAttributes) { mUid = uid; mLocale = locale; mTsListener = tsListener; mScListener = scListener; mBundle = bundle; + mSupportedAttributes = supportedAttributes; } } @@ -825,6 +829,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { final SessionRequest request = mPendingSessionRequests.get(i); mSpellChecker.getISpellCheckerSession( request.mLocale, request.mScListener, request.mBundle, + request.mSupportedAttributes, new ISpellCheckerServiceCallbackBinder(this, request)); mOnGoingSessionRequests.add(request); } @@ -913,6 +918,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { try { mSpellChecker.getISpellCheckerSession( request.mLocale, request.mScListener, request.mBundle, + request.mSupportedAttributes, new ISpellCheckerServiceCallbackBinder(this, request)); mOnGoingSessionRequests.add(request); } catch(RemoteException e) { diff --git a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java new file mode 100644 index 000000000000..fb75ae1609cd --- /dev/null +++ b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java @@ -0,0 +1,203 @@ +/* + * 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.timedetector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.timedetector.GnssTimeSuggestion; +import android.app.timedetector.TimeDetector; +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.LocationRequest; +import android.location.LocationTime; +import android.os.Binder; +import android.os.SystemClock; +import android.os.TimestampedValue; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.server.FgThread; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.time.Duration; + +/** + * Monitors the GNSS time. + * + * <p>When available, the time is always suggested to the {@link + * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device + * system clock, depending on user settings and what other signals are available. + */ +public final class GnssTimeUpdateService extends Binder { + private static final String TAG = "GnssTimeUpdateService"; + private static final boolean D = Log.isLoggable(TAG, Log.DEBUG); + + /** + * Handles the lifecycle events for the GnssTimeUpdateService. + */ + public static class Lifecycle extends SystemService { + private GnssTimeUpdateService mService; + + public Lifecycle(@NonNull Context context) { + super(context); + } + + @Override + public void onStart() { + mService = new GnssTimeUpdateService(getContext()); + publishBinderService("gnss_time_update_service", mService); + } + + @Override + public void onBootPhase(int phase) { + // Need to wait for some location providers to be enabled. If done at + // PHASE_SYSTEM_SERVICES_READY, error where "gps" provider does not exist could occur. + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + // Initiate location updates. On boot, GNSS might not be available right away. + // Instead of polling GNSS time periodically, passive location updates are enabled. + // Once an update is received, the gnss time will be queried and suggested to + // TimeDetectorService. + mService.requestGnssTimeUpdates(); + } + } + } + + private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4); + private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService"; + + private final Context mContext; + private final TimeDetector mTimeDetector; + private final AlarmManager mAlarmManager; + private final LocationManager mLocationManager; + private final LocationManagerInternal mLocationManagerInternal; + + @Nullable private AlarmManager.OnAlarmListener mAlarmListener; + @Nullable private LocationListener mLocationListener; + @Nullable private TimestampedValue<Long> mLastSuggestedGnssTime; + + @VisibleForTesting + GnssTimeUpdateService(@NonNull Context context) { + mContext = context.createAttributionContext(ATTRIBUTION_TAG); + mTimeDetector = mContext.getSystemService(TimeDetector.class); + mLocationManager = mContext.getSystemService(LocationManager.class); + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class); + } + + /** + * Request passive location updates. Such a request will not trigger any active locations or + * power usage itself. + */ + @VisibleForTesting + void requestGnssTimeUpdates() { + if (D) { + Log.d(TAG, "requestGnssTimeUpdates()"); + } + + // Location Listener triggers onLocationChanged() when GNSS data is available, so + // that the getGnssTimeMillis() function doesn't need to be continuously polled. + mLocationListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + if (D) { + Log.d(TAG, "onLocationChanged()"); + } + + // getGnssTimeMillis() can return null when the Master Location Switch for the + // foreground user is disabled. + LocationTime locationTime = mLocationManagerInternal.getGnssTimeMillis(); + if (locationTime != null) { + suggestGnssTime(locationTime); + } else { + if (D) { + Log.d(TAG, "getGnssTimeMillis() returned null"); + } + } + + mLocationManager.removeUpdates(mLocationListener); + mLocationListener = null; + + mAlarmListener = new AlarmManager.OnAlarmListener() { + @Override + public void onAlarm() { + if (D) { + Log.d(TAG, "onAlarm()"); + } + mAlarmListener = null; + requestGnssTimeUpdates(); + } + }; + + // Set next alarm to re-enable location updates. + long next = SystemClock.elapsedRealtime() + + GNSS_TIME_UPDATE_ALARM_INTERVAL.toMillis(); + mAlarmManager.set( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + next, + TAG, + mAlarmListener, + FgThread.getHandler()); + } + }; + + mLocationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL) + .setMinUpdateIntervalMillis(0) + .build(), + FgThread.getExecutor(), + mLocationListener); + } + + /** + * Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector. + */ + private void suggestGnssTime(LocationTime locationTime) { + if (D) { + Log.d(TAG, "suggestGnssTime()"); + } + long gnssTime = locationTime.getTime(); + long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L; + + TimestampedValue<Long> timeSignal = new TimestampedValue<>( + elapsedRealtimeMs, gnssTime); + mLastSuggestedGnssTime = timeSignal; + + GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); + mTimeDetector.suggestGnssTime(timeSuggestion); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime); + pw.print("state: "); + if (mLocationListener != null) { + pw.println("time updates enabled"); + } else { + pw.println("alarm enabled"); + } + } +} diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 0e8fd8fa9f8a..da3e48ad2c0d 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -55,6 +55,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.Authorization; import android.security.KeyStore; import android.service.trust.TrustAgentService; import android.text.TextUtils; @@ -188,6 +189,8 @@ public class TrustManagerService extends SystemService { private boolean mTrustAgentsCanRun = false; private int mCurrentUser = UserHandle.USER_SYSTEM; + private Authorization mAuthorizationService; + public TrustManagerService(Context context) { super(context); mContext = context; @@ -197,6 +200,7 @@ public class TrustManagerService extends SystemService { mStrongAuthTracker = new StrongAuthTracker(context); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mSettingsObserver = new SettingsObserver(mHandler); + mAuthorizationService = new Authorization(); } @Override @@ -699,11 +703,13 @@ public class TrustManagerService extends SystemService { if (changed) { dispatchDeviceLocked(userId, locked); + mAuthorizationService.onLockScreenEvent(locked, userId, null); KeyStore.getInstance().onUserLockedStateChanged(userId, locked); // Also update the user's profiles who have unified challenge, since they // share the same unlocked state (see {@link #isDeviceLocked(int)}) for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) { if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(profileHandle)) { + mAuthorizationService.onLockScreenEvent(locked, profileHandle, null); KeyStore.getInstance().onUserLockedStateChanged(profileHandle, locked); } } @@ -1255,6 +1261,7 @@ public class TrustManagerService extends SystemService { mDeviceLockedForUser.put(userId, locked); } + mAuthorizationService.onLockScreenEvent(locked, userId, null); KeyStore.getInstance().onUserLockedStateChanged(userId, locked); if (locked) { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 7024e67a8204..4e0c0c54923b 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -123,7 +123,7 @@ public class VcnGatewayConnection extends StateMachine { private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST = "Underlying Network lost"; private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; - private static final int TOKEN_ANY = Integer.MIN_VALUE; + private static final int TOKEN_ALL = Integer.MIN_VALUE; private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; private static final int TEARDOWN_TIMEOUT_SECONDS = 5; @@ -139,7 +139,7 @@ public class VcnGatewayConnection extends StateMachine { * * <p>In the Connected state, this MAY indicate a mobility even occurred. * - * @param arg1 The "any" token; this event is always applicable. + * @param arg1 The "all" token; this event is always applicable. * @param obj @NonNull An EventUnderlyingNetworkChangedInfo instance with relevant data. */ private static final int EVENT_UNDERLYING_NETWORK_CHANGED = 1; @@ -175,7 +175,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Upon receipt of this signal, the state machine will transition from the Retry-timeout * state to the Connecting state. * - * @param arg1 The "any" token; no sessions are active in the RetryTimeoutState. + * @param arg1 The "all" token; no sessions are active in the RetryTimeoutState. */ private static final int EVENT_RETRY_TIMEOUT_EXPIRED = 2; @@ -318,7 +318,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Upon receipt of this signal, the state machine MUST tear down all active sessions, cancel * any pending work items, and move to the Disconnected state. * - * @param arg1 The "any" token; this signal is always honored. + * @param arg1 The "all" token; this signal is always honored. * @param obj @NonNull An EventDisconnectRequestedInfo instance with relevant data. */ private static final int EVENT_DISCONNECT_REQUESTED = 7; @@ -504,16 +504,9 @@ public class VcnGatewayConnection extends StateMachine { * <p>Once torn down, this VcnTunnel CANNOT be started again. */ public void teardownAsynchronously() { - mUnderlyingNetworkTracker.teardown(); - - // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down. - if (mTunnelIface != null) { - mTunnelIface.close(); - } - sendMessage( EVENT_DISCONNECT_REQUESTED, - TOKEN_ANY, + TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); quit(); @@ -521,6 +514,16 @@ public class VcnGatewayConnection extends StateMachine { // is also called asynchronously when a NetworkAgent becomes unwanted } + @Override + protected void onQuitting() { + // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down. + if (mTunnelIface != null) { + mTunnelIface.close(); + } + + mUnderlyingNetworkTracker.teardown(); + } + private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @@ -530,26 +533,24 @@ public class VcnGatewayConnection extends StateMachine { if (underlying == null) { sendMessageDelayed( EVENT_DISCONNECT_REQUESTED, - TOKEN_ANY, + TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST), TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS)); - return; - } + } else if (getHandler() != null) { + // Cancel any existing disconnect due to loss of underlying network + // getHandler() can return null if the state machine has already quit. Since this is + // called from other classes, this condition must be verified. - // Cancel any existing disconnect due to loss of underlying network - // getHandler() can return null if the state machine has already quit. Since this is - // called - // from other classes, this condition must be verified. - if (getHandler() != null) { getHandler() .removeEqualMessages( EVENT_DISCONNECT_REQUESTED, new EventDisconnectRequestedInfo( DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); } + sendMessage( EVENT_UNDERLYING_NETWORK_CHANGED, - TOKEN_ANY, + TOKEN_ALL, new EventUnderlyingNetworkChangedInfo(underlying)); } } @@ -594,10 +595,101 @@ public class VcnGatewayConnection extends StateMachine { } private abstract class BaseState extends State { + @Override + public void enter() { + try { + enterState(); + } catch (Exception e) { + Slog.wtf(TAG, "Uncaught exception", e); + sendMessage( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ALL, + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + } + } + protected void enterState() throws Exception {} + /** + * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng + * builds. + */ + @Override + public boolean processMessage(Message msg) { + try { + processStateMsg(msg); + } catch (Exception e) { + Slog.wtf(TAG, "Uncaught exception", e); + sendMessage( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ALL, + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); + } + + return HANDLED; + } + protected abstract void processStateMsg(Message msg) throws Exception; + + protected void logUnhandledMessage(Message msg) { + // Log as unexpected all known messages, and log all else as unknown. + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough + case EVENT_RETRY_TIMEOUT_EXPIRED: // Fallthrough + case EVENT_SESSION_LOST: // Fallthrough + case EVENT_SESSION_CLOSED: // Fallthrough + case EVENT_TRANSFORM_CREATED: // Fallthrough + case EVENT_SETUP_COMPLETED: // Fallthrough + case EVENT_DISCONNECT_REQUESTED: // Fallthrough + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + logUnexpectedEvent(msg.what); + break; + default: + logWtfUnknownEvent(msg.what); + break; + } + } + + protected void teardownNetwork() { + if (mNetworkAgent != null) { + mNetworkAgent.sendNetworkInfo(buildNetworkInfo(false /* isConnected */)); + mNetworkAgent = null; + } + } + + protected void teardownIke() { + if (mIkeSession != null) { + mIkeSession.close(); + } + } + + protected void handleDisconnectRequested(String msg) { + Slog.v(TAG, "Tearing down. Cause: " + msg); + teardownNetwork(); + teardownIke(); + + if (mIkeSession == null) { + // Already disconnected, go straight to DisconnectedState + transitionTo(mDisconnectedState); + } else { + // Still need to wait for full closure + transitionTo(mDisconnectingState); + } + } + + protected void logUnexpectedEvent(int what) { + Slog.d(TAG, String.format( + "Unexpected event code %d in state %s", what, this.getClass().getSimpleName())); + } + + protected void logWtfUnknownEvent(int what) { + Slog.wtf(TAG, String.format( + "Unknown event code %d in state %s", what, this.getClass().getSimpleName())); + } } + /** * State representing the a disconnected VCN tunnel. * @@ -608,7 +700,29 @@ public class VcnGatewayConnection extends StateMachine { protected void processStateMsg(Message msg) {} } - private abstract class ActiveBaseState extends BaseState {} + private abstract class ActiveBaseState extends BaseState { + /** + * Handles all incoming messages, discarding messages for previous networks. + * + * <p>States that handle mobility events may need to override this method to receive + * messages for all underlying networks. + */ + @Override + public boolean processMessage(Message msg) { + final int token = msg.arg1; + // Only process if a valid token is presented. + if (isValidToken(token)) { + return super.processMessage(msg); + } + + Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what); + return HANDLED; + } + + protected boolean isValidToken(int token) { + return (token == TOKEN_ALL || token == mCurrentToken); + } + } /** * Transitive state representing a VCN that is tearing down an IKE session. diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index a4d888b3f9cf..c36375ef0af5 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -39,8 +39,6 @@ import com.google.android.collect.Lists; import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; /** Plays a {@link Vibration} in dedicated thread. */ // TODO(b/159207608): Make this package-private once vibrator services are moved to this package @@ -76,7 +74,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi void onVibrationEnded(long vibrationId, Vibration.Status status); } - private final Object mLock = new Object(); private final WorkSource mWorkSource = new WorkSource(); private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; @@ -84,7 +81,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private final VibrationCallbacks mCallbacks; private final SparseArray<VibratorController> mVibrators; - @GuardedBy("mLock") + @GuardedBy("this") @Nullable private VibrateStep mCurrentVibrateStep; @GuardedBy("this") @@ -147,7 +144,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi /** Notify current vibration that a step has completed on given vibrator. */ public void vibratorComplete(int vibratorId) { - synchronized (mLock) { + synchronized (this) { if (mCurrentVibrateStep != null) { mCurrentVibrateStep.vibratorComplete(vibratorId); } @@ -171,7 +168,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi final int stepCount = steps.size(); for (int i = 0; i < stepCount; i++) { Step step = steps.get(i); - synchronized (mLock) { + synchronized (this) { if (step instanceof VibrateStep) { mCurrentVibrateStep = (VibrateStep) step; } else { @@ -315,27 +312,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } - /** - * Sleeps until given {@link CountDownLatch} has finished or {@code wakeUpTime} was reached. - * - * <p>This stops immediately when {@link #cancel()} is called. - */ - private void awaitUntil(CountDownLatch counter, long wakeUpTime) { - synchronized (this) { - long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - while (counter.getCount() > 0 && durationRemaining > 0) { - try { - counter.await(durationRemaining, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - } - if (mForceStop) { - break; - } - durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - } - } - } - private void noteVibratorOn(long duration) { try { mBatteryStatsService.noteVibratorOn(mVibration.uid, duration); @@ -371,12 +347,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private final class SingleVibrateStep implements VibrateStep { private final VibratorController mVibrator; private final VibrationEffect mEffect; - private final CountDownLatch mCounter; SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) { mVibrator = vibrator; mEffect = effect; - mCounter = new CountDownLatch(1); } @Override @@ -390,7 +364,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return; } mVibrator.off(); - mCounter.countDown(); + synchronized (VibrationThread.this) { + VibrationThread.this.notify(); + } } @Override @@ -408,7 +384,11 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi noteVibratorOn(duration); // Vibration is playing with no need to control amplitudes, just wait for native // callback or timeout. - awaitUntil(mCounter, startTime + duration + CALLBACKS_EXTRA_TIMEOUT); + waitUntil(startTime + duration + CALLBACKS_EXTRA_TIMEOUT); + if (mForceStop) { + mVibrator.off(); + return Vibration.Status.CANCELLED; + } return Vibration.Status.FINISHED; } @@ -499,14 +479,15 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi /** Represent a synchronized vibration step on multiple vibrators. */ private final class SyncedVibrateStep implements VibrateStep { private final SparseArray<VibrationEffect> mEffects; - private final CountDownLatch mActiveVibratorCounter; - private final int mRequiredCapabilities; private final int[] mVibratorIds; + @GuardedBy("VibrationThread.this") + private int mActiveVibratorCounter; + SyncedVibrateStep(SparseArray<VibrationEffect> effects) { mEffects = effects; - mActiveVibratorCounter = new CountDownLatch(mEffects.size()); + mActiveVibratorCounter = mEffects.size(); // TODO(b/159207608): Calculate required capabilities for syncing this step. mRequiredCapabilities = 0; mVibratorIds = new int[effects.size()]; @@ -527,7 +508,11 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return; } mVibrators.get(vibratorId).off(); - mActiveVibratorCounter.countDown(); + synchronized (VibrationThread.this) { + if (--mActiveVibratorCounter <= 0) { + VibrationThread.this.notify(); + } + } } @Override @@ -556,7 +541,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi AmplitudeStep nextStep = step.nextStep(); if (nextStep == null) { // This vibrator has finished playing the effect for this step. - mActiveVibratorCounter.countDown(); + synchronized (VibrationThread.this) { + mActiveVibratorCounter--; + } } else { nextSteps.add(nextStep); } @@ -564,7 +551,16 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi // All OneShot and Waveform effects have finished. Just wait for the other effects // to end via native callbacks before finishing this synced step. - awaitUntil(mActiveVibratorCounter, startTime + timeout + CALLBACKS_EXTRA_TIMEOUT); + synchronized (VibrationThread.this) { + if (mActiveVibratorCounter > 0) { + waitUntil(startTime + timeout + CALLBACKS_EXTRA_TIMEOUT); + } + } + if (mForceStop) { + stopAllVibrators(); + return Vibration.Status.CANCELLED; + } + return Vibration.Status.FINISHED; } finally { if (timeout > 0) { @@ -779,7 +775,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi Slog.d(TAG, "DelayStep of " + mDelay + "ms starting..."); } waitUntil(SystemClock.uptimeMillis() + mDelay); - return Vibration.Status.FINISHED; + return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; } finally { if (DEBUG) { Slog.d(TAG, "DelayStep done."); diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 53f52e286fbd..9dcf12caa55b 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -272,12 +272,18 @@ public final class VibratorController { return 0; } synchronized (mLock) { - mNativeWrapper.compose(effect.getPrimitiveEffects().toArray( - new VibrationEffect.Composition.PrimitiveEffect[0]), vibrationId); + VibrationEffect.Composition.PrimitiveEffect[] primitives = + effect.getPrimitiveEffects().toArray( + new VibrationEffect.Composition.PrimitiveEffect[0]); + mNativeWrapper.compose(primitives, vibrationId); notifyVibratorOnLocked(); // Compose don't actually give us an estimated duration, so we just guess here. - // TODO(b/177807015): use exposed durations from IVibrator here instead - return 20 * effect.getPrimitiveEffects().size(); + long duration = 0; + for (VibrationEffect.Composition.PrimitiveEffect primitive : primitives) { + // TODO(b/177807015): use exposed durations from IVibrator here instead + duration += 20 + primitive.delay; + } + return duration; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0f4bb4c5b081..ba0292dff7a3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2749,8 +2749,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (ensureVisibility) { mDisplayContent.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - false /* preserveWindows */, true /* notifyClients */, - mTaskSupervisor.mUserLeaving); + false /* preserveWindows */, true /* notifyClients */); } } @@ -4823,7 +4822,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A handleAlreadyVisible(); } - void makeInvisible(boolean userLeaving) { + void makeInvisible() { if (!mVisibleRequested) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this); return; @@ -4864,7 +4863,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If the app is capable of entering PIP, we should try pausing it now // so it can PIP correctly. if (deferHidingClient) { - task.startPausingLocked(userLeaving, false /* uiSleeping */, + task.startPausingLocked(false /* uiSleeping */, null /* resuming */, "makeInvisible"); break; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index e67210e42cc8..683bb08f7916 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1588,6 +1588,7 @@ class ActivityStarter { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); startedActivityStack = handleStartResult(r, result); mService.continueWindowLayout(); + mSupervisor.mUserLeaving = false; // Transition housekeeping if (!ActivityManager.isStartResultSuccessful(result)) { @@ -2340,6 +2341,7 @@ class ActivityStarter { mDoResume = false; mAvoidMoveToFront = true; } + mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask()); } mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index b332739820dc..efae16b91e4c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1940,7 +1940,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } else { stack.setWindowingMode(windowingMode); stack.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, - true /* notifyClients */, mTaskSupervisor.mUserLeaving); + true /* notifyClients */); } return true; } finally { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index a68f55750c18..d2ce3695ce6f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1361,53 +1361,58 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return; } - if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mUserLeaving = true; - } - - mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_TO_FRONT, - 0 /* flags */, task, options != null ? options.getRemoteTransition() : null); - reason = reason + " findTaskToMoveToFront"; - boolean reparented = false; - if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { - final Rect bounds = options.getLaunchBounds(); - task.setBounds(bounds); - - Task stack = - mRootWindowContainer.getLaunchRootTask(null, options, task, ON_TOP); - - if (stack != currentStack) { - moveHomeRootTaskToFrontIfNeeded(flags, stack.getDisplayArea(), reason); - task.reparent(stack, ON_TOP, REPARENT_KEEP_ROOT_TASK_AT_FRONT, !ANIMATE, - DEFER_RESUME, reason); - currentStack = stack; - reparented = true; - // task.reparent() should already placed the task on top, - // still need moveTaskToFrontLocked() below for any transition settings. - } - if (stack.shouldResizeRootTaskWithLaunchBounds()) { - stack.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME); - } else { - // WM resizeTask must be done after the task is moved to the correct stack, - // because Task's setBounds() also updates dim layer's bounds, but that has - // dependency on the stack. - task.resize(false /* relayout */, false /* forced */); + try { + if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mUserLeaving = true; + } + + mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_TO_FRONT, + 0 /* flags */, task, options != null ? options.getRemoteTransition() : null); + reason = reason + " findTaskToMoveToFront"; + boolean reparented = false; + if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { + final Rect bounds = options.getLaunchBounds(); + task.setBounds(bounds); + + Task stack = + mRootWindowContainer.getLaunchRootTask(null, options, task, ON_TOP); + + if (stack != currentStack) { + moveHomeRootTaskToFrontIfNeeded(flags, stack.getDisplayArea(), reason); + task.reparent(stack, ON_TOP, REPARENT_KEEP_ROOT_TASK_AT_FRONT, !ANIMATE, + DEFER_RESUME, reason); + currentStack = stack; + reparented = true; + // task.reparent() should already placed the task on top, + // still need moveTaskToFrontLocked() below for any transition settings. + } + if (stack.shouldResizeRootTaskWithLaunchBounds()) { + stack.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME); + } else { + // WM resizeTask must be done after the task is moved to the correct stack, + // because Task's setBounds() also updates dim layer's bounds, but that has + // dependency on the stack. + task.resize(false /* relayout */, false /* forced */); + } } - } - if (!reparented) { - moveHomeRootTaskToFrontIfNeeded(flags, currentStack.getDisplayArea(), reason); - } + if (!reparented) { + moveHomeRootTaskToFrontIfNeeded(flags, currentStack.getDisplayArea(), reason); + } - final ActivityRecord r = task.getTopNonFinishingActivity(); - currentStack.moveTaskToFront(task, false /* noAnimation */, options, - r == null ? null : r.appTimeTracker, reason); + final ActivityRecord r = task.getTopNonFinishingActivity(); + currentStack.moveTaskToFront(task, false /* noAnimation */, options, + r == null ? null : r.appTimeTracker, reason); - if (DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK, - "findTaskToMoveToFront: moved to front of stack=" + currentStack); + if (DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK, + "findTaskToMoveToFront: moved to front of stack=" + currentStack); - handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, - mRootWindowContainer.getDefaultTaskDisplayArea(), currentStack, forceNonResizeable); + handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, + mRootWindowContainer.getDefaultTaskDisplayArea(), currentStack, + forceNonResizeable); + } finally { + mUserLeaving = false; + } } private void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea, @@ -2209,7 +2214,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { .notifyActivityDismissingDockedStack(); taskDisplayArea.onSplitScreenModeDismissed(task); taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, - true /* notifyClients */, mUserLeaving); + true /* notifyClients */); } return; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 976924459252..b7970cdf20d1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1559,6 +1559,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } final int rotation = rotationForActivityInDifferentOrientation(r); if (rotation == ROTATION_UNDEFINED) { + // The display rotation won't be changed by current top activity. If there was fixed + // rotation activity, its rotated state should be cleared to cancel the adjustments. + if (hasTopFixedRotationLaunchingApp() + // Avoid breaking recents animation. + && !mFixedRotationLaunchingApp.getTask().isAnimatingByRecents()) { + clearFixedRotationLaunchingApp(); + } return false; } if (!r.getParent().matchParentBounds()) { @@ -5536,7 +5543,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void ensureActivitiesVisible(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients, boolean userLeaving) { + boolean preserveWindows, boolean notifyClients) { if (mInEnsureActivitiesVisible) { // Don't do recursive work. return; @@ -5546,7 +5553,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp try { forAllRootTasks(rootTask -> { rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients, userLeaving); + notifyClients); }); } finally { mAtmService.mTaskSupervisor.endActivityVisibilityUpdate(); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 45810244fd07..74264d3e5aac 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -33,7 +33,6 @@ class EnsureActivitiesVisibleHelper { private int mConfigChanges; private boolean mPreserveWindows; private boolean mNotifyClients; - private boolean mUserLeaving; EnsureActivitiesVisibleHelper(Task container) { mTask = container; @@ -50,7 +49,7 @@ class EnsureActivitiesVisibleHelper { * be sent to the clients. */ void reset(ActivityRecord starting, int configChanges, boolean preserveWindows, - boolean notifyClients, boolean userLeaving) { + boolean notifyClients) { mStarting = starting; mTop = mTask.topRunningActivity(); // If the top activity is not fullscreen, then we need to make sure any activities under it @@ -61,7 +60,6 @@ class EnsureActivitiesVisibleHelper { mConfigChanges = configChanges; mPreserveWindows = preserveWindows; mNotifyClients = notifyClients; - mUserLeaving = userLeaving; } /** @@ -78,12 +76,10 @@ class EnsureActivitiesVisibleHelper { * @param preserveWindows Flag indicating whether windows should be preserved when updating. * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc * be sent to the clients. - * @param userLeaving Flag indicating whether a userLeaving callback should be issued in the - * case the activity is being set to invisible. */ void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, - boolean notifyClients, boolean userLeaving) { - reset(starting, configChanges, preserveWindows, notifyClients, userLeaving); + boolean notifyClients) { + reset(starting, configChanges, preserveWindows, notifyClients); if (DEBUG_VISIBILITY) { Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop @@ -177,7 +173,7 @@ class EnsureActivitiesVisibleHelper { + " behindFullscreenActivity=" + mBehindFullscreenActivity + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); } - r.makeInvisible(mUserLeaving); + r.makeInvisible(); } if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) { diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ImpressionAttestationController.java index b0afc57b647b..bad6c800ae72 100644 --- a/services/core/java/com/android/server/wm/ImpressionAttestationController.java +++ b/services/core/java/com/android/server/wm/ImpressionAttestationController.java @@ -86,7 +86,7 @@ public class ImpressionAttestationController { private final Handler mHandler; - private final String mSalt; + private final byte[] mSalt; private final float[] mTmpFloat9 = new float[9]; private final Matrix mTmpMatrix = new Matrix(); @@ -99,7 +99,7 @@ public class ImpressionAttestationController { ImpressionAttestationController(Context context) { mContext = context; mHandler = new Handler(Looper.getMainLooper()); - mSalt = UUID.randomUUID().toString(); + mSalt = UUID.randomUUID().toString().getBytes(); } String[] getSupportedImpressionAlgorithms() { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index a20d924de058..cc77cfa1e8e5 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -407,7 +407,7 @@ final class InputMonitor { } mInputFocus = focusToken; - mInputTransaction.setFocusedWindow(mInputFocus, mDisplayId); + mInputTransaction.setFocusedWindow(mInputFocus, focus.getName(), mDisplayId); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus, "reason=UpdateInputWindows"); ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus); diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 7d1da5adf143..c575ca3b7727 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -392,6 +392,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan Slog.e(TAG, "Failed to clean up recents activity", e); throw e; } finally { + mTaskSupervisor.mUserLeaving = false; mService.continueWindowLayout(); // Make sure the surfaces are updated with the latest state. Sometimes the // surface placement may be skipped if display configuration is changed (i.e. diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d8b1e188e34a..4300aed151fc 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1796,8 +1796,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Passing null here for 'starting' param value, so that visibility of actual starting // activity will be properly updated. ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - false /* preserveWindows */, false /* notifyClients */, - mTaskSupervisor.mUserLeaving); + false /* preserveWindows */, false /* notifyClients */); if (displayId == INVALID_DISPLAY) { // The caller didn't provide a valid display id, skip updating config. @@ -1973,15 +1972,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> */ void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows) { - ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */, - mTaskSupervisor.mUserLeaving); + ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */); } /** * @see #ensureActivitiesVisible(ActivityRecord, int, boolean) */ void ensureActivitiesVisible(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients, boolean userLeaving) { + boolean preserveWindows, boolean notifyClients) { if (mTaskSupervisor.inActivityVisibilityUpdate()) { // Don't do recursive work. return; @@ -1993,7 +1991,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent display = getChildAt(displayNdx); display.ensureActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients, userLeaving); + notifyClients); } } finally { mTaskSupervisor.endActivityVisibilityUpdate(); @@ -2858,6 +2856,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); taskDisplayArea = daToken != null ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; + + final Task rootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); + if (rootTask != null) { + return rootTask; + } } // First preference for stack goes to the task Id set in the activity options. Use the stack @@ -3032,7 +3035,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int activityType = options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED ? options.getLaunchActivityType() : r.getActivityType(); - return taskDisplayArea.createRootTask(windowingMode, activityType, true /*onTop*/); + return taskDisplayArea.createRootTask( + windowingMode, activityType, true /*onTop*/, options); } return null; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 2295ee35a4fb..fb50fa1e1b65 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -209,6 +209,7 @@ import android.view.WindowManager.TransitionOldType; import android.window.ITaskOrganizer; import android.window.StartingWindowInfo; import android.window.TaskSnapshot; +import android.window.WindowContainerToken; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -880,6 +881,11 @@ class Task extends WindowContainer<WindowContainer> { EventLogTags.writeWmTaskCreated(mTaskId, isRootTask() ? INVALID_TASK_ID : getRootTaskId()); } + static Task fromWindowContainerToken(WindowContainerToken token) { + if (token == null) return null; + return fromBinder(token.asBinder()).asTask(); + } + Task reuseAsLeafTask(IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, Intent intent, ActivityInfo info, ActivityRecord activity) { voiceSession = _voiceSession; @@ -5600,6 +5606,10 @@ class Task extends WindowContainer<WindowContainer> { return false; } + final boolean startPausingLocked(boolean uiSleeping, ActivityRecord resuming, String reason) { + return startPausingLocked(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason); + } + /** * Start pausing the currently resumed activity. It is an error to call this if there * is already an activity being paused or there is no resumed activity. @@ -5864,8 +5874,7 @@ class Task extends WindowContainer<WindowContainer> { */ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows) { - ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */, - mTaskSupervisor.mUserLeaving); + ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */); } /** @@ -5882,16 +5891,14 @@ class Task extends WindowContainer<WindowContainer> { * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of * {@link mEnsureActivitiesVisibleHelper}. - * @param userLeaving Flag indicating whether a userLeaving callback should be issued in the - * case an activity is being set to invisible. */ // TODO: Should be re-worked based on the fact that each task as a stack in most cases. void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients, boolean userLeaving) { + boolean preserveWindows, boolean notifyClients) { mTaskSupervisor.beginActivityVisibilityUpdate(); try { forAllLeafTasks(task -> task.mEnsureActivitiesVisibleHelper.process( - starting, configChanges, preserveWindows, notifyClients, userLeaving), + starting, configChanges, preserveWindows, notifyClients), true /* traverseTopToBottom */); // Notify WM shell that task visibilities may have changed @@ -6087,11 +6094,6 @@ class Task extends WindowContainer<WindowContainer> { mRootWindowContainer.cancelInitializingActivities(); - // Remember how we'll process this pause/resume situation, and ensure - // that the state is reset however we wind up proceeding. - boolean userLeaving = mTaskSupervisor.mUserLeaving; - mTaskSupervisor.mUserLeaving = false; - if (!hasRunningActivity) { // There are no activities left in the stack, let's look somewhere else. return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options); @@ -6111,7 +6113,7 @@ class Task extends WindowContainer<WindowContainer> { // a fullscreen window forward to cover another freeform activity.) if (taskDisplayArea.inMultiWindowMode()) { taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - false /* preserveWindows */, true /* notifyClients */, userLeaving); + false /* preserveWindows */, true /* notifyClients */); } ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity " + "resumed %s", next); @@ -6176,19 +6178,12 @@ class Task extends WindowContainer<WindowContainer> { // doesn't represent the last resumed activity. However, the last focus stack does if // it isn't null. lastResumed = lastFocusedRootTask.getResumedActivity(); - if (userLeaving && inMultiWindowMode() && lastFocusedRootTask.shouldBeVisible(next)) { - // The user isn't leaving if this stack is the multi-window mode and the last - // focused stack should still be visible. - if(DEBUG_USER_LEAVING) Slog.i(TAG_USER_LEAVING, "Overriding userLeaving to false" - + " next=" + next + " lastResumed=" + lastResumed); - userLeaving = false; - } } - boolean pausing = taskDisplayArea.pauseBackTasks(userLeaving, next); + boolean pausing = taskDisplayArea.pauseBackTasks(next); if (mResumedActivity != null) { ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Pausing %s", mResumedActivity); - pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next, + pausing |= startPausingLocked(false /* uiSleeping */, next, "resumeTopActivityInnerLocked"); } if (pausing) { @@ -7380,6 +7375,7 @@ class Task extends WindowContainer<WindowContainer> { task = new Task.Builder(mAtmService) .setTaskId(taskId) .setActivityInfo(info) + .setActivityOptions(options) .setIntent(intent) .setVoiceSession(voiceSession) .setVoiceInteractor(voiceInteractor) @@ -7819,6 +7815,7 @@ class Task extends WindowContainer<WindowContainer> { private int mMinWidth = INVALID_MIN_SIZE; private int mMinHeight = INVALID_MIN_SIZE; private ActivityInfo mActivityInfo; + private ActivityOptions mActivityOptions; private IVoiceInteractionSession mVoiceSession; private IVoiceInteractor mVoiceInteractor; private int mActivityType; @@ -7872,6 +7869,11 @@ class Task extends WindowContainer<WindowContainer> { return this; } + Builder setActivityOptions(ActivityOptions opts) { + mActivityOptions = opts; + return this; + } + Builder setVoiceSession(IVoiceInteractionSession voiceSession) { mVoiceSession = voiceSession; return this; @@ -8078,7 +8080,7 @@ class Task extends WindowContainer<WindowContainer> { // Task created by organizer are added as root. final Task launchRootTask = mCreatedByOrganizer - ? null : tda.getLaunchRootTask(mWindowingMode, mActivityType); + ? null : tda.getLaunchRootTask(mWindowingMode, mActivityType, mActivityOptions); if (launchRootTask != null) { // Since this task will be put into a root task, its windowingMode will be // inherited. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 0136c010429b..a83f81d23372 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -48,6 +48,7 @@ import android.os.UserHandle; import android.util.IntArray; import android.util.Slog; import android.view.SurfaceControl; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -997,11 +998,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * Returns an existing stack compatible with the windowing mode and activity type or creates one * if a compatible stack doesn't exist. * - * @see #getOrCreateRootTask(int, int, boolean, Intent, Task) + * @see #getOrCreateRootTask(int, int, boolean, Intent, Task, ActivityOptions) */ Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop) { return getOrCreateRootTask(windowingMode, activityType, onTop, null /* intent */, - null /* candidateTask */); + null /* candidateTask */, null /* options */); } /** @@ -1014,7 +1015,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * @see #createRootTask(int, int, boolean) */ Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop, - Intent intent, Task candidateTask) { + Intent intent, Task candidateTask, ActivityOptions options) { // Need to pass in a determined windowing mode to see if a new stack should be created, // so use its parent's windowing mode if it is undefined. if (!alwaysCreateRootTask( @@ -1027,7 +1028,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } else if (candidateTask != null) { final Task stack = candidateTask; final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; - final Task launchRootTask = getLaunchRootTask(windowingMode, activityType); + final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options); if (launchRootTask != null) { if (stack.getParent() == null) { @@ -1054,6 +1055,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { .setOnTop(onTop) .setParent(this) .setIntent(intent) + .setActivityOptions(options) .build(); } @@ -1074,7 +1076,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // it's display's windowing mode. windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); return getOrCreateRootTask(windowingMode, activityType, onTop, null /* intent */, - candidateTask); + candidateTask, options); } @VisibleForTesting @@ -1082,6 +1084,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return mAtmService.mTaskSupervisor.getNextTaskIdForUser(); } + Task createRootTask(int windowingMode, int activityType, boolean onTop) { + return createRootTask(windowingMode, activityType, onTop, null /* activityOptions */); + } + /** * A convinenit method of creating a root task by providing windowing mode and activity type * on this display. @@ -1095,14 +1101,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}. * @param onTop If true the root task will be created at the top of the display, * else at the bottom. + * @param opts The activity options. * @return The newly created root task. */ - Task createRootTask(int windowingMode, int activityType, boolean onTop) { + Task createRootTask(int windowingMode, int activityType, boolean onTop, ActivityOptions opts) { return new Task.Builder(mAtmService) .setWindowingMode(windowingMode) .setActivityType(activityType) .setParent(this) .setOnTop(onTop) + .setActivityOptions(opts) .build(); } @@ -1134,7 +1142,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } - Task getLaunchRootTask(int windowingMode, int activityType) { + Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) { + // Try to use the launch root task in options if available. + if (options != null) { + final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); + // We only allow this for created by organizer tasks. + if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) { + return launchRootTask; + } + } + for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) { if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) { return mLaunchRootTasks.get(i).task; @@ -1297,11 +1314,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * pause activities in visible root tasks, so if an activity is launched within the same root * task, hen we should explicitly pause that root task's top activity. * - * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @param resuming The resuming activity. * @return {@code true} if any activity was paused as a result of this call. */ - boolean pauseBackTasks(boolean userLeaving, ActivityRecord resuming) { + boolean pauseBackTasks(ActivityRecord resuming) { final int[] someActivityPaused = {0}; forAllLeafTasks((task) -> { final ActivityRecord resumedActivity = task.getResumedActivity(); @@ -1310,7 +1326,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { || !task.isTopActivityFocusable())) { ProtoLog.d(WM_DEBUG_STATES, "pauseBackTasks: task=%s " + "mResumedActivity=%s", task, resumedActivity); - if (task.startPausingLocked(userLeaving, false /* uiSleeping*/, + if (task.startPausingLocked(false /* uiSleeping*/, resuming, "pauseBackTasks")) { someActivityPaused[0]++; } @@ -1816,12 +1832,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } void ensureActivitiesVisible(ActivityRecord starting, int configChanges, - boolean preserveWindows, boolean notifyClients, boolean userLeaving) { + boolean preserveWindows, boolean notifyClients) { mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate(); try { forAllRootTasks(rootTask -> { rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients, userLeaving); + notifyClients); }); } finally { mAtmService.mTaskSupervisor.endActivityVisibilityUpdate(); @@ -1866,7 +1882,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Reparent task to corresponding launch root or display area. final WindowContainer launchRoot = task.supportsSplitScreenWindowingMode() ? toDisplayArea.getLaunchRootTask( - task.getWindowingMode(), task.getActivityType()) + task.getWindowingMode(), task.getActivityType(), null /* options */) : null; task.reparent(launchRoot == null ? toDisplayArea : launchRoot, POSITION_TOP); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4156ed602464..ad884699f755 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2339,7 +2339,7 @@ public class WindowManagerService extends IWindowManager.Stub if (shouldRelayout) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); - result = win.relayoutVisibleWindow(result, attrChanges); + result = win.relayoutVisibleWindow(result); try { result = createSurfaceControl(outSurfaceControl, result, win, winAnimator); @@ -8411,10 +8411,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - void grantEmbeddedWindowFocus(Session session, IBinder targetInputToken, boolean grantFocus) { + void grantEmbeddedWindowFocus(Session session, IBinder inputToken, boolean grantFocus) { synchronized (mGlobalLock) { final EmbeddedWindowController.EmbeddedWindow embeddedWindow = - mEmbeddedWindowController.get(targetInputToken); + mEmbeddedWindowController.get(inputToken); if (embeddedWindow == null) { Slog.e(TAG, "Embedded window not found"); return; @@ -8426,7 +8426,7 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.Transaction t = mTransactionFactory.get(); final int displayId = embeddedWindow.mDisplayId; if (grantFocus) { - t.setFocusedWindow(targetInputToken, displayId).apply(); + t.setFocusedWindow(inputToken, embeddedWindow.getName(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + embeddedWindow.getName(), "reason=grantEmbeddedWindowFocus(true)"); @@ -8441,7 +8441,8 @@ public class WindowManagerService extends IWindowManager.Stub embeddedWindow.getName()); return; } - t.requestFocusTransfer(newFocusTarget.mInputChannelToken, targetInputToken, + t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(), + inputToken, embeddedWindow.getName(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + newFocusTarget, @@ -8477,13 +8478,17 @@ public class WindowManagerService extends IWindowManager.Stub } SurfaceControl.Transaction t = mTransactionFactory.get(); if (grantFocus) { - t.requestFocusTransfer(targetInputToken, hostWindow.mInputChannel.getToken(), + t.requestFocusTransfer(targetInputToken, embeddedWindow.getName(), + hostWindow.mInputChannel.getToken(), + hostWindow.getName(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + embeddedWindow.getName(), "reason=grantEmbeddedWindowFocus(true)"); } else { - t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), targetInputToken, + t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(), + targetInputToken, + embeddedWindow.getName(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + hostWindow, diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 389f428a4138..0d37f6088b8f 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -843,7 +843,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) { final Task task = mPreQTopResumedActivity.getTask(); if (task != null) { - task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, + boolean userLeaving = task.shouldBeVisible(null); + task.startPausingLocked(userLeaving, false /* uiSleeping */, activity, "top-resumed-changed"); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b7602fec73df..621b9719cd39 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -55,7 +55,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SCALED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; -import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; @@ -68,7 +67,6 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; -import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; @@ -3049,8 +3047,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return; } - if (mAttrs.type == TYPE_APPLICATION_OVERLAY && mSession.mCanCreateSystemApplicationOverlay - && (mAttrs.privateFlags & SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY) != 0) { + if (mAttrs.type == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay() + && mSession.mCanCreateSystemApplicationOverlay) { return; } @@ -4935,7 +4933,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets); } - int relayoutVisibleWindow(int result, int attrChanges) { + int relayoutVisibleWindow(int result) { final boolean wasVisible = isVisible(); result |= (!wasVisible || !isDrawn()) ? RELAYOUT_RES_FIRST_TIME : 0; @@ -4970,18 +4968,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - if ((attrChanges & FORMAT_CHANGED) != 0) { - // If the format can't be changed in place, preserve the old surface until the app draws - // on the new one. This prevents blinking when we change elevation of freeform and - // pinned windows. - if (!mWinAnimator.tryChangeFormatInPlaceLocked()) { - mWinAnimator.preserveSurfaceLocked(getSyncTransaction()); - result |= RELAYOUT_RES_SURFACE_CHANGED - | RELAYOUT_RES_FIRST_TIME; - scheduleAnimation(); - } - } - // When we change the Surface size, in scenarios which may require changing // the surface position in sync with the resize, we use a preserved surface // so we can freeze it while waiting for the client to report draw on the newly diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 9e0fee3927c3..64e8184e793f 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -647,11 +647,6 @@ class WindowToken extends WindowContainer<WindowState> { state.mIsTransforming = false; if (applyDisplayRotation != null) { applyDisplayRotation.run(); - } else { - // The display will not rotate to the rotation of this container, let's cancel them. - for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) { - state.mAssociatedTokens.get(i).cancelFixedRotationTransform(); - } } // The state is cleared at the end, because it is used to indicate that other windows can // use seamless rotation when applying rotation to display. @@ -659,6 +654,10 @@ class WindowToken extends WindowContainer<WindowState> { final WindowToken token = state.mAssociatedTokens.get(i); token.mFixedRotationTransformState = null; token.notifyFixedRotationTransform(false /* enabled */); + if (applyDisplayRotation == null) { + // Notify cancellation because the display does not change rotation. + token.cancelFixedRotationTransform(); + } } } @@ -707,7 +706,6 @@ class WindowToken extends WindowContainer<WindowState> { // The window may be detached or detaching. return; } - notifyFixedRotationTransform(false /* enabled */); final int originalRotation = getWindowConfiguration().getRotation(); onConfigurationChanged(parent.getConfiguration()); onCancelFixedRotationTransform(originalRotation); diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 1cb9e5786a70..7d705c1fac69 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -35,6 +35,18 @@ </xs:element> <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/> <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" /> + <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease"> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastIncrease"> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="nonNegativeDecimal" name="screenBrightnessRampSlowDecrease"> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="nonNegativeDecimal" name="screenBrightnessRampSlowIncrease"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index e073ab3675ca..eb3f1b751963 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -7,10 +7,18 @@ package com.android.server.display.config { method public com.android.server.display.config.DisplayQuirks getQuirks(); method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault(); method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap(); + method public final java.math.BigDecimal getScreenBrightnessRampFastDecrease(); + method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); + method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); + method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public void setQuirks(com.android.server.display.config.DisplayQuirks); method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal); method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap); + method public final void setScreenBrightnessRampFastDecrease(java.math.BigDecimal); + method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal); + method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal); + method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal); } public class DisplayQuirks { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 55ba6c902e47..1194099c1145 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -17,8 +17,11 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; +import android.app.admin.ManagedProfileProvisioningParams; import android.content.ComponentName; +import android.os.UserHandle; import android.util.Slog; import com.android.server.SystemService; @@ -115,4 +118,13 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void setOrganizationIdForUser( @NonNull String callerPackage, @NonNull String enterpriseId, int userId) {} + + public UserHandle createAndProvisionManagedProfile( + @NonNull ManagedProfileProvisioningParams provisioningParams) { + return null; + } + + public void provisionFullyManagedDevice( + FullyManagedDeviceProvisioningParams provisioningParams) { + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8a9ec080dcd6..12b3f40cd646 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -21,8 +21,11 @@ import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; @@ -34,6 +37,7 @@ import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS; import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT; import static android.app.admin.DevicePolicyManager.CODE_OK; +import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; import static android.app.admin.DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER; @@ -83,16 +87,25 @@ import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_PRE_CONDITION_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_PROFILE_CREATION_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED; +import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; +import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; @@ -109,10 +122,14 @@ import static com.android.server.pm.UserManagerInternal.OWNER_TYPE_DEVICE_OWNER; import static com.android.server.pm.UserManagerInternal.OWNER_TYPE_PROFILE_OWNER; import static com.android.server.pm.UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE; +import android.Manifest; import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -146,6 +163,8 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; +import android.app.admin.FullyManagedDeviceProvisioningParams; +import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; @@ -267,6 +286,7 @@ import android.view.inputmethod.InputMethodInfo; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.LocalePicker; import com.android.internal.logging.MetricsLogger; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.NetworkUtilsInternal; @@ -325,12 +345,14 @@ import java.util.Date; 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.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Implementation of the device policy APIs. @@ -380,6 +402,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String NULL_STRING_ARRAY = "nullStringArray"; + private static final String ALLOW_USER_PROVISIONING_KEY = "ro.config.allowuserprovisioning"; + // Comprehensive list of delegations. private static final String DELEGATIONS[] = { DELEGATION_CERT_INSTALL, @@ -1231,6 +1255,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return LocalServices.getService(LockSettingsInternal.class); } + CrossProfileApps getCrossProfileApps() { + return mContext.getSystemService(CrossProfileApps.class); + } + boolean hasUserSetupCompleted(DevicePolicyData userData) { return userData.mUserSetupComplete; } @@ -11327,7 +11355,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void showLocationSettingsEnabledNotification(UserHandle user) { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags(FLAG_ACTIVITY_NEW_TASK); // Fill the component explicitly to prevent the PendingIntent from being intercepted // and fired with crafted target. b/155183624 ActivityInfo targetInfo = intent.resolveActivityInfo( @@ -12188,7 +12216,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); intent.putExtra(Intent.EXTRA_USER_ID, userId); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); return intent; } @@ -12666,6 +12694,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { logMissingFeatureAction("Cannot check provisioning for action " + action); return CODE_DEVICE_ADMIN_NOT_SUPPORTED; } + if (!isProvisioningAllowed()) { + return CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; + } final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName); if (code != CODE_OK) { Slog.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName @@ -12675,6 +12706,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return code; } + /** + * Checks if provisioning is allowed during regular usage (non-developer/CTS). This could + * return {@code false} if the device has an overlaid config value set to false. If not set, + * the default is true. + */ + private boolean isProvisioningAllowed() { + boolean isDeveloperMode = isDeveloperMode(mContext); + boolean isProvisioningAllowedForNormalUsers = SystemProperties.getBoolean( + ALLOW_USER_PROVISIONING_KEY, /* defValue= */ true); + + return isDeveloperMode || isProvisioningAllowedForNormalUsers; + } + + private static boolean isDeveloperMode(Context context) { + return Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 0) > 0; + } + private int checkProvisioningPreConditionSkipPermissionNoLog(String action, String packageName) { final int callingUserId = mInjector.userHandleGetCallingUserId(); @@ -12831,14 +12879,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { callingUserHandle, hasDeviceOwner)); return CODE_CANNOT_ADD_MANAGED_PROFILE; } - // If there's a restriction on removing the managed profile then we have to take it - // into account when checking whether more profiles can be added. - boolean canRemoveProfile = - !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - callingUserHandle); - if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { - Slog.i(LOG_TAG, String.format( - "Cannot add more profiles: Can remove current? %b", canRemoveProfile)); + + // Bail out if we are trying to provision a work profile but one already exists. + if (!mUserManager.canAddMoreManagedProfiles( + callingUserId, /* allowedToRemoveOne= */ false)) { + Slog.i(LOG_TAG, String.format("A work profile already exists.")); return CODE_CANNOT_ADD_MANAGED_PROFILE; } } finally { @@ -13765,7 +13810,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final Uri packageURI = Uri.parse("package:" + packageName); final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); - uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + uninstallIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(uninstallIntent, UserHandle.of(userId)); } @@ -15957,4 +16002,415 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setBoolean(isManagedProfile(userId)) .write(); } + + @Override + public UserHandle createAndProvisionManagedProfile( + @NonNull ManagedProfileProvisioningParams provisioningParams) { + final ComponentName admin = provisioningParams.getProfileAdminComponentName(); + Objects.requireNonNull(admin, "admin is null"); + + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + UserInfo userInfo = null; + final long identity = Binder.clearCallingIdentity(); + try { + final int result = checkProvisioningPreConditionSkipPermission( + ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName()); + if (result != CODE_OK) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_PRE_CONDITION_FAILED, + "Provisioning preconditions failed with result: " + result); + } + + final Set<String> nonRequiredApps = provisioningParams.isLeaveAllSystemAppsEnabled() + ? Collections.emptySet() + : mOverlayPackagesProvider.getNonRequiredApps( + admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE); + userInfo = mUserManager.createProfileForUserEvenWhenDisallowed( + provisioningParams.getProfileName(), + UserManager.USER_TYPE_PROFILE_MANAGED, + UserInfo.FLAG_DISABLED, + caller.getUserId(), + nonRequiredApps.toArray(new String[nonRequiredApps.size()])); + if (userInfo == null) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_PROFILE_CREATION_FAILED, + "Error creating profile, createProfileForUserEvenWhenDisallowed " + + "returned null."); + } + + resetInteractAcrossProfilesAppOps(); + installExistingAdminPackage(userInfo.id, admin.getPackageName()); + if (!enableAdminAndSetProfileOwner( + userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED, + "Error setting profile owner."); + } + setUserSetupComplete(userInfo.id); + + startUser(userInfo.id); + maybeMigrateAccount( + userInfo.id, caller.getUserId(), provisioningParams.getAccountToMigrate(), + provisioningParams.isKeepAccountMigrated()); + + if (provisioningParams.isOrganizationOwnedProvisioning()) { + markIsProfileOwnerOnOrganizationOwnedDevice(admin, userInfo.id); + restrictRemovalOfManagedProfile(admin, userInfo.id); + } + + final Intent intent = new Intent(DevicePolicyManager.ACTION_MANAGED_PROFILE_CREATED) + .putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id) + .putExtra( + DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, + provisioningParams.isLeaveAllSystemAppsEnabled()) + .setPackage(getManagedProvisioningPackage(mContext)) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + + return userInfo.getUserHandle(); + } catch (Exception e) { + // in case of any errors during provisioning, remove the newly created profile. + if (userInfo != null) { + mUserManager.removeUserEvenWhenDisallowed(userInfo.id); + } + throw e; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void resetInteractAcrossProfilesAppOps() { + mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps(); + pregrantDefaultInteractAcrossProfilesAppOps(); + } + + private void pregrantDefaultInteractAcrossProfilesAppOps() { + final String op = + AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES); + for (String packageName : getConfigurableDefaultCrossProfilePackages()) { + if (appOpIsChangedFromDefault(op, packageName)) { + continue; + } + mInjector.getCrossProfileApps().setInteractAcrossProfilesAppOp( + packageName, MODE_ALLOWED); + } + } + + private Set<String> getConfigurableDefaultCrossProfilePackages() { + List<String> defaultPackages = getDefaultCrossProfilePackages(); + return defaultPackages.stream().filter( + mInjector.getCrossProfileApps()::canConfigureInteractAcrossProfiles).collect( + Collectors.toSet()); + } + + private boolean appOpIsChangedFromDefault(String op, String packageName) { + try { + final int uid = mContext.getPackageManager().getPackageUid( + packageName, /* flags= */ 0); + return mInjector.getAppOpsManager().unsafeCheckOpNoThrow( + op, uid, packageName) + != AppOpsManager.MODE_DEFAULT; + } catch (NameNotFoundException e) { + return false; + } + } + + private void installExistingAdminPackage(int userId, String packageName) { + try { + final int status = mContext.getPackageManager().installExistingPackageAsUser( + packageName, + userId); + if (status != PackageManager.INSTALL_SUCCEEDED) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED, + String.format("Failed to install existing package %s for user %d with " + + "result code %d", + packageName, userId, status)); + } + } catch (NameNotFoundException e) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED, + String.format("Failed to install existing package %s for user %d: %s", + packageName, userId, e.getMessage())); + } + } + + private boolean enableAdminAndSetProfileOwner( + @UserIdInt int userId, @UserIdInt int callingUserId, ComponentName adminComponent, + String ownerName) { + enableAndSetActiveAdmin(userId, callingUserId, adminComponent); + return setProfileOwner(adminComponent, ownerName, userId); + } + + private void enableAndSetActiveAdmin( + @UserIdInt int userId, @UserIdInt int callingUserId, ComponentName adminComponent) { + final String adminPackage = adminComponent.getPackageName(); + enablePackage(adminPackage, callingUserId); + setActiveAdmin(adminComponent, /* refreshing= */ true, userId); + } + + private void enablePackage(String packageName, @UserIdInt int userId) { + try { + final int enabledSetting = mIPackageManager.getApplicationEnabledSetting( + packageName, userId); + if (enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + && enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + mIPackageManager.setApplicationEnabledSetting( + packageName, + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + // Device policy app may have launched ManagedProvisioning, play nice and + // don't kill it as a side-effect of this call. + PackageManager.DONT_KILL_APP, + userId, + mContext.getOpPackageName()); + } + } catch (RemoteException e) { + // Shouldn't happen. + } + } + + private void setUserSetupComplete(@UserIdInt int userId) { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), USER_SETUP_COMPLETE, 1, userId); + } + + private void startUser(@UserIdInt int userId) throws IllegalStateException { + final UserUnlockedBlockingReceiver unlockedReceiver = new UserUnlockedBlockingReceiver( + userId); + mContext.registerReceiverAsUser( + unlockedReceiver, + new UserHandle(userId), + new IntentFilter(Intent.ACTION_USER_UNLOCKED), + /* broadcastPermission = */ null, + /* scheduler= */ null); + try { + if (!mInjector.getIActivityManager().startUserInBackground(userId)) { + throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED, + String.format("Unable to start user %d in background", userId)); + } + + if (!unlockedReceiver.waitForUserUnlocked()) { + throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED, + String.format("Timeout whilst waiting for unlock of user %d.", userId)); + } + } catch (RemoteException e) { + // Shouldn't happen. + } finally { + mContext.unregisterReceiver(unlockedReceiver); + } + } + + void maybeMigrateAccount( + @UserIdInt int targetUserId, @UserIdInt int sourceUserId, Account accountToMigrate, + boolean keepAccountMigrated) { + final UserHandle sourceUser = UserHandle.of(sourceUserId); + final UserHandle targetUser = UserHandle.of(targetUserId); + if (accountToMigrate == null) { + Slog.d(LOG_TAG, "No account to migrate."); + return; + } + if (sourceUser.equals(targetUser)) { + Slog.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account."); + return; + } + copyAccount(targetUser, sourceUser, accountToMigrate); + if (!keepAccountMigrated) { + removeAccount(accountToMigrate); + } + } + + void copyAccount(UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate) { + try { + final AccountManager accountManager = mContext.getSystemService(AccountManager.class); + final boolean copySucceeded = accountManager.copyAccountToUser( + accountToMigrate, + sourceUser, + targetUser, + /* callback= */ null, /* handler= */ null) + .getResult(60 * 3, TimeUnit.SECONDS); + if (!copySucceeded) { + Slog.e(LOG_TAG, "Failed to copy account to " + targetUser); + } + } catch (OperationCanceledException | AuthenticatorException | IOException e) { + // Account migration is not considered a critical operation. + Slog.e(LOG_TAG, "Exception copying account to " + targetUser, e); + } + } + + void removeAccount(Account account) { + final AccountManager accountManager = + mContext.getSystemService(AccountManager.class); + final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account, + null, null /* callback */, null /* handler */); + try { + final Bundle result = bundle.getResult(); + if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, /* default */ false)) { + Slog.i(LOG_TAG, "Account removed from the primary user."); + } else { + // TODO(174768447): Revisit start activity logic. + final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT); + removeIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); + if (removeIntent != null) { + Slog.i(LOG_TAG, "Starting activity to remove account"); + new Handler(Looper.getMainLooper()).post(() -> { + mContext.startActivity(removeIntent); + }); + } else { + Slog.e(LOG_TAG, "Could not remove account from the primary user."); + } + } + } catch (OperationCanceledException | AuthenticatorException | IOException e) { + Slog.e(LOG_TAG, "Exception removing account from the primary user.", e); + } + } + + private void markIsProfileOwnerOnOrganizationOwnedDevice( + ComponentName admin, @UserIdInt int profileId) { + getDpmForProfile(profileId).markProfileOwnerOnOrganizationOwnedDevice(admin); + } + + private void restrictRemovalOfManagedProfile( + ComponentName admin, @UserIdInt int profileId) { + getDpmForProfile(profileId).addUserRestriction( + admin, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); + } + + private DevicePolicyManager getDpmForProfile(@UserIdInt int profileId) { + final Context profileContext = mContext.createContextAsUser( + UserHandle.of(profileId), /* flags= */ 0); + return profileContext.getSystemService(DevicePolicyManager.class); + } + + @Override + public void provisionFullyManagedDevice( + FullyManagedDeviceProvisioningParams provisioningParams) { + ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName(); + + Objects.requireNonNull(deviceAdmin, "admin is null."); + Objects.requireNonNull(provisioningParams.getOwnerName(), "owner name is null."); + + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + final long identity = Binder.clearCallingIdentity(); + try { + int result = checkProvisioningPreConditionSkipPermission( + ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName()); + if (result != CODE_OK) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_PRE_CONDITION_FAILED, + "Provisioning preconditions failed with result: " + result); + } + + setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime()); + setLocale(provisioningParams.getLocale()); + + if (!removeNonRequiredAppsForManagedDevice( + caller.getUserId(), + provisioningParams.isLeaveAllSystemAppsEnabled(), + deviceAdmin)) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED, + "PackageManager failed to remove non required apps."); + } + + if (!setActiveAdminAndDeviceOwner( + caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) { + throw new ServiceSpecificException( + PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED, + "Failed to set device owner."); + } + + disallowAddUser(); + + final Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISIONED_MANAGED_DEVICE) + .putExtra(Intent.EXTRA_USER_HANDLE, caller.getUserId()) + .putExtra( + DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, + provisioningParams.isLeaveAllSystemAppsEnabled()) + .setPackage(getManagedProvisioningPackage(mContext)) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void setTimeAndTimezone(String timeZone, long localTime) { + try { + final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + if (timeZone != null) { + alarmManager.setTimeZone(timeZone); + } + if (localTime > 0) { + alarmManager.setTime(localTime); + } + } catch (Exception e) { + // Do not stop provisioning and ignore this error. + Slog.e(LOG_TAG, "Alarm manager failed to set the system time/timezone.", e); + } + } + + private void setLocale(Locale locale) { + if (locale == null || locale.equals(Locale.getDefault())) { + return; + } + try { + // If locale is different from current locale this results in a configuration change, + // which will trigger the restarting of the activity. + LocalePicker.updateLocale(locale); + } catch (Exception e) { + // Do not stop provisioning and ignore this error. + Slog.e(LOG_TAG, "Failed to set the system locale.", e); + } + } + + private boolean removeNonRequiredAppsForManagedDevice( + int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) { + Set<String> packagesToDelete = leaveAllSystemAppsEnabled + ? Collections.emptySet() + : mOverlayPackagesProvider.getNonRequiredApps( + admin, userId, ACTION_PROVISION_MANAGED_DEVICE); + if (packagesToDelete.isEmpty()) { + return true; + } + NonRequiredPackageDeleteObserver packageDeleteObserver = + new NonRequiredPackageDeleteObserver(packagesToDelete.size()); + for (String packageName : packagesToDelete) { + if (isPackageInstalledForUser(packageName, userId)) { + Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId); + mContext.getPackageManager().deletePackageAsUser( + packageName, + packageDeleteObserver, + PackageManager.DELETE_SYSTEM_APP, + userId); + } + } + Slog.i(LOG_TAG, "Waiting for non required apps to be deleted"); + return packageDeleteObserver.awaitPackagesDeletion(); + } + + private void disallowAddUser() { + if (mInjector.userManagerIsHeadlessSystemUserMode()) { + Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode."); + return; + } + for (UserInfo userInfo : mUserManager.getUsers()) { + UserHandle userHandle = userInfo.getUserHandle(); + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { + mUserManager.setUserRestriction( + UserManager.DISALLOW_ADD_USER, /* value= */ true, userHandle); + } + } + } + + private boolean setActiveAdminAndDeviceOwner( + @UserIdInt int userId, ComponentName adminComponent, String name) { + enableAndSetActiveAdmin(userId, userId, adminComponent); + return setDeviceOwner(adminComponent, name, userId); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java b/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java new file mode 100644 index 000000000000..0e448cdb244c --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java @@ -0,0 +1,70 @@ +/* + * 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.devicepolicy; + +import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; + +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.PackageManager; +import android.util.Log; +import android.util.Slog; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Awaits the deletion of all the non-required apps. + */ +final class NonRequiredPackageDeleteObserver extends IPackageDeleteObserver.Stub { + private static final int PACKAGE_DELETE_TIMEOUT_SEC = 30; + + private final AtomicInteger mPackageCount = new AtomicInteger(/* initialValue= */ 0); + private final CountDownLatch mLatch; + private boolean mSuccess; + + NonRequiredPackageDeleteObserver(int packageCount) { + this.mLatch = new CountDownLatch(packageCount); + this.mPackageCount.set(packageCount); + } + + @Override + public void packageDeleted(String packageName, int returnCode) { + if (returnCode != PackageManager.DELETE_SUCCEEDED) { + Slog.e(LOG_TAG, "Failed to delete package: " + packageName); + mLatch.notifyAll(); + return; + } + int currentPackageCount = mPackageCount.decrementAndGet(); + if (currentPackageCount == 0) { + mSuccess = true; + Slog.i(LOG_TAG, "All non-required system apps with launcher icon, " + + "and all disallowed apps have been uninstalled."); + } + mLatch.countDown(); + } + + public boolean awaitPackagesDeletion() { + try { + mLatch.await(PACKAGE_DELETE_TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.w(LOG_TAG, "Interrupted while waiting for package deletion", e); + Thread.currentThread().interrupt(); + } + return mSuccess; + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UserUnlockedBlockingReceiver.java b/services/devicepolicy/java/com/android/server/devicepolicy/UserUnlockedBlockingReceiver.java new file mode 100644 index 000000000000..4ce96b7c427b --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/UserUnlockedBlockingReceiver.java @@ -0,0 +1,59 @@ +/* + * 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.devicepolicy; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * BroadcastReceiver that listens to {@link Intent#ACTION_USER_UNLOCKED} in order to provide + * a blocking wait until the managed profile has been started and unlocked. + */ +class UserUnlockedBlockingReceiver extends BroadcastReceiver { + private static final int WAIT_FOR_USER_UNLOCKED_TIMEOUT_SECONDS = 120; + + private final Semaphore mSemaphore = new Semaphore(0); + private final int mUserId; + + UserUnlockedBlockingReceiver(int userId) { + mUserId = userId; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + return; + } + if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == mUserId) { + mSemaphore.release(); + } + } + + public boolean waitForUserUnlocked() { + try { + return mSemaphore.tryAcquire( + WAIT_FOR_USER_UNLOCKED_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + return false; + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 57b3e8da8885..c613dfb22c77 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -323,6 +323,8 @@ public final class SystemServer implements Dumpable { "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle"; private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS = "com.android.server.location.timezone.LocationTimeZoneManagerService$Lifecycle"; + private static final String GNSS_TIME_UPDATE_SERVICE_CLASS = + "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle"; private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS = "com.android.server.accessibility.AccessibilityManagerService$Lifecycle"; private static final String ADB_SERVICE_CLASS = @@ -1874,6 +1876,16 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) { + t.traceBegin("StartGnssTimeUpdateService"); + try { + mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS); + } catch (Throwable e) { + reportWtf("starting GnssTimeUpdateService service", e); + } + t.traceEnd(); + } + if (!isWatch) { t.traceBegin("StartSearchManagerService"); try { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index c038a0f33904..28a6ff7e3287 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -124,7 +124,7 @@ public class FullScreenMagnificationGestureHandlerTest { private Context mContext; FullScreenMagnificationController mFullScreenMagnificationController; @Mock - MagnificationGestureHandler.ScaleChangedListener mMockScaleChangedListener; + MagnificationGestureHandler.Callback mMockCallback; @Mock MagnificationRequestObserver mMagnificationRequestObserver; @Mock @@ -179,7 +179,7 @@ public class FullScreenMagnificationGestureHandlerTest { private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap, boolean detectShortcutTrigger) { FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( - mContext, mFullScreenMagnificationController, mMockScaleChangedListener, + mContext, mFullScreenMagnificationController, mMockCallback, detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController, DISPLAY_0); mHandler = new TestHandler(h.mDetectingState, mClock) { @@ -451,6 +451,14 @@ public class FullScreenMagnificationGestureHandlerTest { verify(mWindowMagnificationPromptController).showNotificationIfNeeded(); } + @Test + public void testZoomedWithTripleTap_callsOnTripleTapped() { + goFromStateIdleTo(STATE_ZOOMED_2TAPS); + + verify(mMockCallback).onTripleTapped(DISPLAY_0, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + private void assertActionsInOrder(List<MotionEvent> actualEvents, List<Integer> expectedActions) { assertTrue(actualEvents.size() == expectedActions.size()); @@ -554,8 +562,6 @@ public class FullScreenMagnificationGestureHandlerTest { check(mMgh.mCurrentState == mMgh.mPanningScalingState, state); check(mMgh.mPanningScalingState.mScaling, state); - verify(mMockScaleChangedListener).onMagnificationScaleChanged(DISPLAY_0, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } break; default: throw new IllegalArgumentException("Illegal state: " + state); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index cba618bf94b8..0c3640c4eeb0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -109,6 +109,8 @@ public class MagnificationControllerTest { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mMagnificationController = spy(new MagnificationController(mService, new Object(), mContext, mScreenMagnificationController, mWindowMagnificationManager)); + mMagnificationController.setMagnificationCapabilities( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); } @After @@ -278,34 +280,128 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager).setScale(eq(TEST_DISPLAY), eq(newScale)); verify(mWindowMagnificationManager).persistScale(eq(TEST_DISPLAY)); - verify(mMagnificationController).onMagnificationScaleChanged(eq(TEST_DISPLAY), + } + + @Test + public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void onTouchInteractionEnd_fullScreenAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void onTouchInteractionStart_windowModeAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_WINDOW); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); } @Test - public void onMagnificationScaleChanged_capabilitiesAllMode_showMagnificationButton() + public void onTouchInteractionEnd_windowModeAndCapabilitiesAll_showMagnificationButton() throws RemoteException { - mMagnificationController.setMagnificationCapabilities( - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + setMagnificationEnabled(MODE_WINDOW); - mMagnificationController.onMagnificationScaleChanged(TEST_DISPLAY, MODE_WINDOW); + mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_WINDOW); verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); } @Test - public void onMagnificationScaleChanged_capabilitiesNotAllMode_notShowMagnificationButton() + public void onTouchInteractionChanged_notCapabilitiesAll_notShowMagnificationButton() throws RemoteException { mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + setMagnificationEnabled(MODE_FULLSCREEN); - mMagnificationController.onMagnificationScaleChanged(TEST_DISPLAY, MODE_WINDOW); + mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN); + mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN); verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void onShortcutTriggered_windowModeEnabledAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationController.onShortcutTriggered(TEST_DISPLAY, MODE_WINDOW); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); } + @Test + public void onShortcutTriggered_fullscreenEnabledAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.onShortcutTriggered(TEST_DISPLAY, MODE_FULLSCREEN); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void onShortcutTriggered_windowModeDisabled_removeMagnificationButton() + throws RemoteException { + + mMagnificationController.onShortcutTriggered(TEST_DISPLAY, MODE_WINDOW); + + verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); + } + + @Test + public void onTripleTap_windowModeEnabledAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationController.onTripleTapped(TEST_DISPLAY, MODE_WINDOW); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_WINDOW)); + } + + @Test + public void onTripleTap_fullscreenEnabledAndCapabilitiesAll_showMagnificationButton() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.onTripleTapped(TEST_DISPLAY, MODE_FULLSCREEN); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void onTripleTap_windowModeDisabled_removeMagnificationButton() + throws RemoteException { + + mMagnificationController.onTripleTapped(TEST_DISPLAY, MODE_WINDOW); + + verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); + } + private void setMagnificationEnabled(int mode) throws RemoteException { setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java new file mode 100644 index 000000000000..514d16a0149c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_UP; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.testng.AssertJUnit.assertTrue; + +import android.annotation.NonNull; +import android.provider.Settings; +import android.view.InputDevice; +import android.view.MotionEvent; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link MagnificationGestureHandler}. + */ +@RunWith(AndroidJUnit4.class) +public class MagnificationGestureHandlerTest { + + private TestMagnificationGestureHandler mMgh; + private static final int DISPLAY_0 = 0; + private static final int FULLSCREEN_MODE = + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + + @Mock + MagnificationGestureHandler.Callback mCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mMgh = new TestMagnificationGestureHandler(DISPLAY_0, + /* detectTripleTap= */true, + /* detectShortcutTrigger= */true, + mCallback); + } + + @Test + public void onMotionEvent_isFromScreen_onMotionEventInternal() { + final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + + mMgh.onMotionEvent(downEvent, downEvent, /* policyFlags= */ 0); + + try { + assertTrue(mMgh.mIsInternalMethodCalled); + } finally { + downEvent.recycle(); + } + } + + @Test + public void onMotionEvent_downEvent_handleInteractionStart() { + final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + + mMgh.onMotionEvent(downEvent, downEvent, /* policyFlags= */ 0); + + try { + verify(mCallback).onTouchInteractionStart(eq(DISPLAY_0), eq(mMgh.getMode())); + } finally { + downEvent.recycle(); + } + } + + @Test + public void onMotionEvent_upEvent_handleInteractionEnd() { + final MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); + upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + + mMgh.onMotionEvent(upEvent, upEvent, /* policyFlags= */ 0); + + try { + verify(mCallback).onTouchInteractionEnd(eq(DISPLAY_0), eq(mMgh.getMode())); + } finally { + upEvent.recycle(); + } + } + + @Test + public void onMotionEvent_cancelEvent_handleInteractionEnd() { + final MotionEvent cancelEvent = MotionEvent.obtain(0, 0, ACTION_CANCEL, 0, 0, 0); + cancelEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + + mMgh.onMotionEvent(cancelEvent, cancelEvent, /* policyFlags= */ 0); + + try { + verify(mCallback).onTouchInteractionEnd(eq(DISPLAY_0), eq(mMgh.getMode())); + } finally { + cancelEvent.recycle(); + } + } + + + @Test + public void notifyShortcutTriggered_callsOnShortcutTriggered() { + mMgh.notifyShortcutTriggered(); + + verify(mCallback).onShortcutTriggered(eq(DISPLAY_0), eq(mMgh.getMode())); + } + + private static class TestMagnificationGestureHandler extends MagnificationGestureHandler { + + boolean mIsInternalMethodCalled = false; + + TestMagnificationGestureHandler(int displayId, boolean detectTripleTap, + boolean detectShortcutTrigger, @NonNull Callback callback) { + super(displayId, detectTripleTap, detectShortcutTrigger, callback); + } + + @Override + void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mIsInternalMethodCalled = true; + } + + @Override + public void notifyShortcutTriggered() { + super.notifyShortcutTriggered(); + } + + @Override + public void handleShortcutTriggered() { + } + + @Override + public int getMode() { + return FULLSCREEN_MODE; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index 9f930da4ee29..4b7ebbc29b46 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -19,7 +19,9 @@ package com.android.server.accessibility.magnification; import static com.android.server.testutils.TestUtils.strictMock; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.Rect; @@ -38,6 +40,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.function.IntConsumer; @@ -48,7 +52,7 @@ import java.util.function.IntConsumer; public class WindowMagnificationGestureHandlerTest { public static final int STATE_IDLE = 1; - public static final int STATE_SHOW_MAGNIFIER = 2; + public static final int STATE_SHOW_MAGNIFIER_SHORTCUT = 2; public static final int STATE_TWO_FINGERS_DOWN = 3; public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4; //TODO: Test it after can injecting Handler to GestureMatcher is available. @@ -65,16 +69,18 @@ public class WindowMagnificationGestureHandlerTest { private WindowMagnificationManager mWindowMagnificationManager; private MockWindowMagnificationConnection mMockConnection; private WindowMagnificationGestureHandler mWindowMagnificationGestureHandler; + @Mock + MagnificationGestureHandler.Callback mMockCallback; @Before public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getContext(); mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0, mock(WindowMagnificationManager.Callback.class)); mMockConnection = new MockWindowMagnificationConnection(); mWindowMagnificationGestureHandler = new WindowMagnificationGestureHandler( - mContext, mWindowMagnificationManager, mock( - MagnificationGestureHandler.ScaleChangedListener.class), + mContext, mWindowMagnificationManager, mMockCallback, /** detectTripleTap= */true, /** detectShortcutTrigger= */true, DISPLAY_0); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class)); @@ -140,6 +146,14 @@ public class WindowMagnificationGestureHandlerTest { }); } + @Test + public void onTripleTap_callsOnTripleTapped() { + goFromStateIdleTo(STATE_SHOW_MAGNIFIER_TRIPLE_TAP); + + verify(mMockCallback).onTripleTapped(eq(DISPLAY_0), + eq(mWindowMagnificationGestureHandler.getMode())); + } + private void forEachState(IntConsumer action) { for (int state = FIRST_STATE; state <= LAST_STATE; state++) { action.accept(state); @@ -159,7 +173,7 @@ public class WindowMagnificationGestureHandlerTest { == mWindowMagnificationGestureHandler.mDetectingState, state); } break; - case STATE_SHOW_MAGNIFIER: + case STATE_SHOW_MAGNIFIER_SHORTCUT: case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: { check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState @@ -188,12 +202,12 @@ public class WindowMagnificationGestureHandlerTest { // no op } break; - case STATE_SHOW_MAGNIFIER: { + case STATE_SHOW_MAGNIFIER_SHORTCUT: { triggerShortcut(); } break; case STATE_TWO_FINGERS_DOWN: { - goFromStateIdleTo(STATE_SHOW_MAGNIFIER); + goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); final Rect frame = mMockConnection.getMirrorWindowFrame(); send(downEvent(frame.centerX(), frame.centerY())); //Second finger is outside the window. @@ -225,14 +239,14 @@ public class WindowMagnificationGestureHandlerTest { // no op } break; - case STATE_SHOW_MAGNIFIER: { + case STATE_SHOW_MAGNIFIER_SHORTCUT: { mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, false); } break; case STATE_TWO_FINGERS_DOWN: { final Rect frame = mMockConnection.getMirrorWindowFrame(); send(upEvent(frame.centerX(), frame.centerY())); - returnToNormalFrom(STATE_SHOW_MAGNIFIER); + returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT); } break; case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: { diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java new file mode 100644 index 000000000000..b5f49124483e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java @@ -0,0 +1,426 @@ +/* + * 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. + */ + +// TODO(b/169883602): This is purposely a different package from the path so that it can access +// AppSearchImpl's methods without having to make them public. This should be replaced by proper +// global query integration tests that can test AppSearchImpl-VisibilityStore integration logic. +package com.android.server.appsearch.external.localstorage; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.appsearch.AppSearchSchema; +import android.app.appsearch.GenericDocument; +import android.app.appsearch.PackageIdentifier; +import android.app.appsearch.SearchResultPage; +import android.app.appsearch.SearchSpec; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; + +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.Collections; +import java.util.List; + +/** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */ +public class AppSearchImplPlatformTest { + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private MockPackageManager mMockPackageManager = new MockPackageManager(); + private Context mContext; + private AppSearchImpl mAppSearchImpl; + private int mGlobalQuerierUid; + + @Before + public void setUp() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + mContext = + new ContextWrapper(context) { + @Override + public PackageManager getPackageManager() { + return mMockPackageManager.getMockPackageManager(); + } + }; + + // Give ourselves global query permissions + mAppSearchImpl = + AppSearchImpl.create( + mTemporaryFolder.newFolder(), + mContext, + mContext.getUserId(), + mContext.getPackageName()); + mGlobalQuerierUid = + mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0); + } + /** + * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term + * test until we have official support for multiple-apps indexing at once. + */ + @Test + public void testGlobalQueryWithMultiplePackages_noPackageFilters() throws Exception { + // Insert package1 schema + List<AppSearchSchema> schema1 = + ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schema1, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Insert package2 schema + List<AppSearchSchema> schema2 = + ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); + mAppSearchImpl.setSchema( + "package2", + "database2", + schema2, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Insert package1 document + GenericDocument document1 = + new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); + mAppSearchImpl.putDocument("package1", "database1", document1); + + // Insert package2 document + GenericDocument document2 = + new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); + mAppSearchImpl.putDocument("package2", "database2", document2); + + // No query filters specified, global query can retrieve all documents. + SearchSpec searchSpec = + new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build(); + SearchResultPage searchResultPage = + mAppSearchImpl.globalQuery( + "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid); + assertThat(searchResultPage.getResults()).hasSize(2); + + // Document2 will be first since it got indexed later and has a "better", aka more recent + // score. + assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); + assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1); + } + + /** + * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term + * test until we have official support for multiple-apps indexing at once. + */ + @Test + public void testGlobalQueryWithMultiplePackages_withPackageFilters() throws Exception { + // Insert package1 schema + List<AppSearchSchema> schema1 = + ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schema1, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Insert package2 schema + List<AppSearchSchema> schema2 = + ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); + mAppSearchImpl.setSchema( + "package2", + "database2", + schema2, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Insert package1 document + GenericDocument document1 = + new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); + mAppSearchImpl.putDocument("package1", "database1", document1); + + // Insert package2 document + GenericDocument document2 = + new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); + mAppSearchImpl.putDocument("package2", "database2", document2); + + // "package1" filter specified + SearchSpec searchSpec = + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_PREFIX) + .addFilterPackageNames("package1") + .build(); + SearchResultPage searchResultPage = + mAppSearchImpl.globalQuery( + "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid); + assertThat(searchResultPage.getResults()).hasSize(1); + assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1); + + // "package2" filter specified + searchSpec = + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_PREFIX) + .addFilterPackageNames("package2") + .build(); + searchResultPage = + mAppSearchImpl.globalQuery( + "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid); + assertThat(searchResultPage.getResults()).hasSize(1); + assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); + } + + @Test + public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + // Make sure foo package will pass package manager checks. + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + + // Set schema1 + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "schema1", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), + /*forceOverride=*/ false); + + // "schema1" is platform hidden now and package visible to package1 + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema1", mGlobalQuerierUid)) + .isFalse(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo)) + .isTrue(); + + // Add a new schema, and include the already-existing "schema1" + mAppSearchImpl.setSchema( + "package", + "database", + ImmutableList.of( + new AppSearchSchema.Builder("schema1").build(), + new AppSearchSchema.Builder("schema2").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "schema1", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), + /*forceOverride=*/ false); + + // Check that "schema1" still has the same visibility settings + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema1", mGlobalQuerierUid)) + .isFalse(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo)) + .isTrue(); + + // "schema2" has default visibility settings + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema2", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema2", uidFoo)) + .isFalse(); + } + + @Test + public void testRemoveSchema_removedFromVisibilityStore() throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + // Make sure foo package will pass package manager checks. + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "schema1", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), + /*forceOverride=*/ false); + + // "schema1" is platform hidden now and package accessible + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema1", mGlobalQuerierUid)) + .isFalse(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo)) + .isTrue(); + + // Remove "schema1" by force overriding + mAppSearchImpl.setSchema( + "package", + "database", + /*schemas=*/ Collections.emptyList(), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ true); + + // Check that "schema1" is no longer considered platform hidden or package accessible + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema1", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo)) + .isFalse(); + + // Add "schema1" back, it gets default visibility settings which means it's not platform + // hidden and not package accessible + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "schema1", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo)) + .isFalse(); + } + + @Test + public void testSetSchema_defaultPlatformVisible() throws Exception { + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "Schema", mGlobalQuerierUid)) + .isTrue(); + } + + @Test + public void testSetSchema_platformHidden() throws Exception { + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "Schema", mGlobalQuerierUid)) + .isFalse(); + } + + @Test + public void testSetSchema_defaultNotPackageAccessible() throws Exception { + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller( + prefix, prefix + "Schema", /*callerUid=*/ 42)) + .isFalse(); + } + + @Test + public void testSetSchema_packageAccessible() throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + // Make sure foo package will pass package manager checks. + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + + String prefix = AppSearchImpl.createPrefix("package", "database"); + mAppSearchImpl.setSchema( + "package", + "database", + Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "Schema", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), + /*forceOverride=*/ false); + assertThat( + mAppSearchImpl + .getVisibilityStoreLocked() + .isSchemaSearchableByCaller(prefix, prefix + "Schema", uidFoo)) + .isTrue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java new file mode 100644 index 000000000000..459fc53dcc08 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java @@ -0,0 +1,88 @@ +/* + * 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. + */ + +// TODO(b/169883602): This is purposely a different package from the path so that AppSearchImplTest +// can use it without an extra import. This should be moved into a proper package once +// AppSearchImpl-VisibilityStore's dependencies are refactored. +package com.android.server.appsearch.external.localstorage; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Mock to help test package name, UID, and certificate verification. */ +public class MockPackageManager { + + @Mock private PackageManager mMockPackageManager; + + public MockPackageManager() { + MockitoAnnotations.initMocks(this); + } + + @NonNull + public PackageManager getMockPackageManager() { + return mMockPackageManager; + } + + /** Mock a NameNotFoundException if the package name isn't installed. */ + public void mockThrowsNameNotFoundException(String packageName) { + try { + when(mMockPackageManager.getPackageUidAsUser(eq(packageName), /*userId=*/ anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); + when(mMockPackageManager.getPackageUidAsUser( + eq(packageName), /*flags=*/ anyInt(), /*userId=*/ anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't ever happen since we're mocking the exception + e.printStackTrace(); + } + } + + /** Mocks that {@code uid} contains the {@code packageName} */ + public void mockGetPackageUidAsUser(String packageName, @UserIdInt int callerUserId, int uid) { + try { + when(mMockPackageManager.getPackageUidAsUser(eq(packageName), eq(callerUserId))) + .thenReturn(uid); + when(mMockPackageManager.getPackageUidAsUser( + eq(packageName), /*flags=*/ anyInt(), eq(callerUserId))) + .thenReturn(uid); + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't ever happen since we're mocking the method. + e.printStackTrace(); + } + } + + /** Mocks that {@code packageName} has been signed with {@code sha256Cert}. */ + public void mockAddSigningCertificate(String packageName, byte[] sha256Cert) { + when(mMockPackageManager.hasSigningCertificate( + packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256)) + .thenReturn(true); + } + + /** Mocks that {@code packageName} has NOT been signed with {@code sha256Cert}. */ + public void mockRemoveSigningCertificate(String packageName, byte[] sha256Cert) { + when(mMockPackageManager.hasSigningCertificate( + packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256)) + .thenReturn(false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java new file mode 100644 index 000000000000..8d35ebe8e2e1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java @@ -0,0 +1,324 @@ +/* + * 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. + */ + +// TODO(b/169883602): This is purposely a different package from the path so that it can access +// AppSearchImpl and VisibilityStore methods without having to make methods public. This should be +// moved into a proper package once AppSearchImpl-VisibilityStore's dependencies are refactored. +package com.android.server.appsearch.external.localstorage; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.appsearch.PackageIdentifier; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; + +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.Collections; + +public class VisibilityStoreTest { + + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private MockPackageManager mMockPackageManager = new MockPackageManager(); + private Context mContext; + private AppSearchImpl mAppSearchImpl; + private VisibilityStore mVisibilityStore; + private int mGlobalQuerierUid; + + @Before + public void setUp() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + mContext = + new ContextWrapper(context) { + @Override + public PackageManager getPackageManager() { + return mMockPackageManager.getMockPackageManager(); + } + }; + + // Give ourselves global query permissions + mAppSearchImpl = + AppSearchImpl.create( + mTemporaryFolder.newFolder(), + mContext, + mContext.getUserId(), + /*globalQuerierPackage=*/ mContext.getPackageName()); + mGlobalQuerierUid = + mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0); + + mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked(); + } + + /** + * Make sure that we don't conflict with any special characters that AppSearchImpl has reserved. + */ + @Test + public void testValidPackageName() { + assertThat(VisibilityStore.PACKAGE_NAME) + .doesNotContain( + "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences + assertThat(VisibilityStore.PACKAGE_NAME) + .doesNotContain( + "" + + AppSearchImpl + .DATABASE_DELIMITER); // Convert the chars to CharSequences + } + + /** + * Make sure that we don't conflict with any special characters that AppSearchImpl has reserved. + */ + @Test + public void testValidDatabaseName() { + assertThat(VisibilityStore.DATABASE_NAME) + .doesNotContain( + "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences + assertThat(VisibilityStore.DATABASE_NAME) + .doesNotContain( + "" + + AppSearchImpl + .DATABASE_DELIMITER); // Convert the chars to CharSequences + } + + @Test + public void testSetVisibility_platformSurfaceable() throws Exception { + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of( + "prefix/schema1", "prefix/schema2"), + /*schemasPackageAccessible=*/ Collections.emptyMap()); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema1", mGlobalQuerierUid)) + .isFalse(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema2", mGlobalQuerierUid)) + .isFalse(); + + // New .setVisibility() call completely overrides previous visibility settings. So + // "schema2" isn't preserved. + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of( + "prefix/schema1", "prefix/schema3"), + /*schemasPackageAccessible=*/ Collections.emptyMap()); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema1", mGlobalQuerierUid)) + .isFalse(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema2", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema3", mGlobalQuerierUid)) + .isFalse(); + + // Everything defaults to visible again. + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), + /*schemasPackageAccessible=*/ Collections.emptyMap()); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema1", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema2", mGlobalQuerierUid)) + .isTrue(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schema3", mGlobalQuerierUid)) + .isTrue(); + } + + @Test + public void testIsSchemaSearchableByCaller_platformQuerierHandlesNameNotFoundException() + throws Exception { + // Initialized the VisibilityStore with this context's package name as the global querier. + mMockPackageManager.mockThrowsNameNotFoundException(mContext.getPackageName()); + + // Create a new VisibilityStore instance since we look up the UID on initialization + AppSearchImpl appSearchImpl = + AppSearchImpl.create( + mTemporaryFolder.newFolder(), + mContext, + mContext.getUserId(), + /*globalQuerierPackage=*/ mContext.getPackageName()); + VisibilityStore visibilityStore = appSearchImpl.getVisibilityStoreLocked(); + + // Use some arbitrary callerUid. If we can't find the global querier's uid though, + // nothing should be platform surfaceable. + assertThat( + visibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", /*callerUid=*/ 0)) + .isFalse(); + } + + @Test + public void testSetVisibility_packageAccessible() throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + // Values for a "bar" client + String packageNameBar = "packageBar"; + byte[] sha256CertBar = new byte[] {100}; + int uidBar = 2; + + // Can't be the same value as uidFoo nor uidBar + int uidNotFooOrBar = 3; + + // By default, a schema isn't package accessible. + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isFalse(); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaBar", uidBar)) + .isFalse(); + + // Grant package access + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "prefix/schemaFoo", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo)), + "prefix/schemaBar", + ImmutableList.of(new PackageIdentifier(packageNameBar, sha256CertBar)))); + + // Should fail if PackageManager doesn't see that it has the proper certificate + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockRemoveSigningCertificate(packageNameFoo, sha256CertFoo); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isFalse(); + + // Should fail if PackageManager doesn't think the package belongs to the uid + mMockPackageManager.mockGetPackageUidAsUser( + packageNameFoo, mContext.getUserId(), uidNotFooOrBar); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isFalse(); + + // But if uid and certificate match, then we should have access + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isTrue(); + + mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar); + mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaBar", uidBar)) + .isTrue(); + + // New .setVisibility() call completely overrides previous visibility settings. So + // "schemaBar" settings aren't preserved. + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "prefix/schemaFoo", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo)))); + + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isTrue(); + + mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar); + mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar); + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaBar", uidBar)) + .isFalse(); + } + + @Test + public void testIsSchemaSearchableByCaller_packageAccessibilityHandlesNameNotFoundException() + throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + // Pretend we can't find the Foo package. + mMockPackageManager.mockThrowsNameNotFoundException(packageNameFoo); + + // Grant package access + mVisibilityStore.setVisibility( + "prefix", + /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "prefix/schemaFoo", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo)))); + + // If we can't verify the Foo package that has access, assume it doesn't have access. + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + "prefix", "prefix/schemaFoo", uidFoo)) + .isFalse(); + } + + @Test + public void testEmptyPrefix() throws Exception { + // Values for a "foo" client + String packageNameFoo = "packageFoo"; + byte[] sha256CertFoo = new byte[] {10}; + int uidFoo = 1; + + mVisibilityStore.setVisibility( + /*prefix=*/ "", + /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), + /*schemasPackageAccessible=*/ ImmutableMap.of( + "schema", + ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo)))); + + assertThat( + mVisibilityStore.isSchemaSearchableByCaller( + /*prefix=*/ "", "schema", mGlobalQuerierUid)) + .isTrue(); + + mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo); + mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo); + assertThat(mVisibilityStore.isSchemaSearchableByCaller(/*prefix=*/ "", "schema", uidFoo)) + .isTrue(); + } +} 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 6366155a4a39..8d744c40e914 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java @@ -16,17 +16,23 @@ package com.android.server.appsearch.external.localstorage; +import static android.app.appsearch.AppSearchResult.RESULT_OK; + import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.expectThrows; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; -import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; +import android.app.appsearch.SetSchemaResult; import android.app.appsearch.exceptions.AppSearchException; +import android.content.Context; +import android.util.ArraySet; + +import androidx.test.core.app.ApplicationProvider; import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter; import com.android.server.appsearch.proto.DocumentProto; @@ -41,7 +47,6 @@ import com.android.server.appsearch.proto.StringIndexingConfig; import com.android.server.appsearch.proto.TermMatchType; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Before; @@ -52,6 +57,7 @@ import org.junit.rules.TemporaryFolder; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; public class AppSearchImplTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); @@ -59,7 +65,15 @@ public class AppSearchImplTest { @Before public void setUp() throws Exception { - mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder()); + Context context = ApplicationProvider.getApplicationContext(); + // Give ourselves global query permissions + mAppSearchImpl = + AppSearchImpl.create( + mTemporaryFolder.newFolder(), + context, + VisibilityStore.NO_OP_USER_ID, + /*globalQuerierPackage + =*/ context.getPackageName()); } // TODO(b/175430168) add test to verify reset is working properly. @@ -460,7 +474,8 @@ public class AppSearchImplTest { // Rewrite SearchSpec mAppSearchImpl.rewriteSearchSpecForPrefixesLocked( searchSpecProto, - Collections.singleton(AppSearchImpl.createPrefix("package", "database"))); + Collections.singleton(AppSearchImpl.createPrefix("package", "database")), + ImmutableSet.of("package$database/type")); assertThat(searchSpecProto.getSchemaTypeFiltersList()) .containsExactly("package$database/type"); assertThat(searchSpecProto.getNamespaceFiltersList()) @@ -505,7 +520,10 @@ public class AppSearchImplTest { searchSpecProto, ImmutableSet.of( AppSearchImpl.createPrefix("package", "database1"), - AppSearchImpl.createPrefix("package", "database2"))); + AppSearchImpl.createPrefix("package", "database2")), + ImmutableSet.of( + "package$database1/typeA", "package$database1/typeB", + "package$database2/typeA", "package$database2/typeB")); assertThat(searchSpecProto.getSchemaTypeFiltersList()) .containsExactly( "package$database1/typeA", @@ -517,6 +535,38 @@ public class AppSearchImplTest { } @Test + public void testRewriteSearchSpec_ignoresSearchSpecSchemaFilters() throws Exception { + SearchSpecProto.Builder searchSpecProto = + SearchSpecProto.newBuilder().setQuery("").addSchemaTypeFilters("type"); + + // Insert schema + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Insert document + GenericDocument document = + new GenericDocument.Builder<>("uri", "type").setNamespace("namespace").build(); + mAppSearchImpl.putDocument("package", "database", document); + + // If 'allowedPrefixedSchemas' is empty, this returns false since there's nothing to + // search over. Despite the searchSpecProto having schema type filters. + assertThat( + mAppSearchImpl.rewriteSearchSpecForPrefixesLocked( + searchSpecProto, + Collections.singleton( + AppSearchImpl.createPrefix("package", "database")), + /*allowedPrefixedSchemas=*/ Collections.emptySet())) + .isFalse(); + } + + @Test public void testQueryEmptyDatabase() throws Exception { SearchSpec searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); @@ -640,119 +690,12 @@ public class AppSearchImplTest { public void testGlobalQueryEmptyDatabase() throws Exception { SearchSpec searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); - SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec); + SearchResultPage searchResultPage = + mAppSearchImpl.globalQuery( + "", searchSpec, /*callerPackageName=*/ "", /*callerUid=*/ 0); assertThat(searchResultPage.getResults()).isEmpty(); } - /** - * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term - * test until we have official support for multiple-apps indexing at once. - */ - @Test - public void testGlobalQueryWithMultiplePackages_noPackageFilters() throws Exception { - // Insert package1 schema - List<AppSearchSchema> schema1 = - ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); - mAppSearchImpl.setSchema( - "package1", - "database1", - schema1, - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - - // Insert package2 schema - List<AppSearchSchema> schema2 = - ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); - mAppSearchImpl.setSchema( - "package2", - "database2", - schema2, - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - - // Insert package1 document - GenericDocument document1 = - new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); - mAppSearchImpl.putDocument("package1", "database1", document1); - - // Insert package2 document - GenericDocument document2 = - new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); - mAppSearchImpl.putDocument("package2", "database2", document2); - - // No query filters specified, global query can retrieve all documents. - SearchSpec searchSpec = - new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); - SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec); - assertThat(searchResultPage.getResults()).hasSize(2); - - // Document2 will be first since it got indexed later and has a "better", aka more recent - // score. - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); - assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1); - } - - /** - * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term - * test until we have official support for multiple-apps indexing at once. - */ - @Test - public void testGlobalQueryWithMultiplePackages_withPackageFilters() throws Exception { - // Insert package1 schema - List<AppSearchSchema> schema1 = - ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); - mAppSearchImpl.setSchema( - "package1", - "database1", - schema1, - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - - // Insert package2 schema - List<AppSearchSchema> schema2 = - ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); - mAppSearchImpl.setSchema( - "package2", - "database2", - schema2, - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - - // Insert package1 document - GenericDocument document1 = - new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); - mAppSearchImpl.putDocument("package1", "database1", document1); - - // Insert package2 document - GenericDocument document2 = - new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); - mAppSearchImpl.putDocument("package2", "database2", document2); - - // "package1" filter specified - SearchSpec searchSpec = - new SearchSpec.Builder() - .setTermMatch(TermMatchType.Code.PREFIX_VALUE) - .addFilterPackageNames("package1") - .build(); - SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec); - assertThat(searchResultPage.getResults()).hasSize(1); - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1); - - // "package2" filter specified - searchSpec = - new SearchSpec.Builder() - .setTermMatch(TermMatchType.Code.PREFIX_VALUE) - .addFilterPackageNames("package2") - .build(); - searchResultPage = mAppSearchImpl.globalQuery("", searchSpec); - assertThat(searchResultPage.getResults()).hasSize(1); - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); - } - @Test public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception { SearchSpec searchSpec = @@ -794,7 +737,8 @@ public class AppSearchImplTest { SchemaProto.newBuilder() .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Email")) + .setSchemaType("package$database1/Email") + .setVersion(0)) .build(); List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>(); @@ -805,67 +749,51 @@ public class AppSearchImplTest { } @Test - public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "schema1", ImmutableList.of(package1)), - /*forceOverride=*/ false); - - // "schema1" is platform hidden now and package visible to package1 - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema1")) - .isFalse(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema1", package1)) - .isTrue(); + public void testSetSchema_incompatible() throws Exception { + List<SchemaTypeConfigProto> existingSchemas = + mAppSearchImpl.getSchemaProtoLocked().getTypesList(); - // Add a new schema, and include the already-existing "schema1" + List<AppSearchSchema> oldSchemas = new ArrayList<>(); + oldSchemas.add( + new AppSearchSchema.Builder("Email") + .addProperty( + new AppSearchSchema.StringPropertyConfig.Builder("foo") + .setCardinality( + AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) + .setTokenizerType( + AppSearchSchema.StringPropertyConfig + .TOKENIZER_TYPE_PLAIN) + .setIndexingType( + AppSearchSchema.StringPropertyConfig + .INDEXING_TYPE_PREFIXES) + .build()) + .build()); + oldSchemas.add(new AppSearchSchema.Builder("Text").build()); + // Set schema Email to AppSearch database1 mAppSearchImpl.setSchema( "package", - "database", - ImmutableList.of( - new AppSearchSchema.Builder("schema1").build(), - new AppSearchSchema.Builder("schema2").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "schema1", ImmutableList.of(package1)), + "database1", + oldSchemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), /*forceOverride=*/ false); - // Check that "schema1" still has the same visibility settings - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema1")) - .isFalse(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema1", package1)) - .isTrue(); + // Create incompatible schema + List<AppSearchSchema> newSchemas = + Collections.singletonList(new AppSearchSchema.Builder("Email").build()); - // "schema2" has default visibility settings - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema2")) - .isTrue(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema2", package1)) - .isFalse(); + // set email incompatible and delete text + SetSchemaResult setSchemaResult = + mAppSearchImpl.setSchema( + "package", + "database1", + newSchemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ true); + assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Text"); + assertThat(setSchemaResult.getIncompatibleSchemaTypes()).containsExactly("Email"); + assertThat(setSchemaResult.getResultCode()).isEqualTo(RESULT_OK); } @Test @@ -891,10 +819,12 @@ public class AppSearchImplTest { SchemaProto.newBuilder() .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Email")) + .setSchemaType("package$database1/Email") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Document")) + .setSchemaType("package$database1/Document") + .setVersion(0)) .build(); // Check both schema Email and Document saved correctly. @@ -906,20 +836,17 @@ public class AppSearchImplTest { final List<AppSearchSchema> finalSchemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build()); - // Check the incompatible error has been thrown. - AppSearchException e = - expectThrows( - AppSearchException.class, - () -> - mAppSearchImpl.setSchema( - "package", - "database1", - finalSchemas, - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false)); - assertThat(e).hasMessageThat().contains("Schema is incompatible"); - assertThat(e).hasMessageThat().contains("Deleted types: [package$database1/Document]"); + SetSchemaResult setSchemaResult = + mAppSearchImpl.setSchema( + "package", + "database1", + finalSchemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false); + + // Check the Document type has been deleted. + assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Document"); // ForceOverride to delete. mAppSearchImpl.setSchema( @@ -935,7 +862,8 @@ public class AppSearchImplTest { SchemaProto.newBuilder() .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Email")) + .setSchemaType("package$database1/Email") + .setVersion(0)) .build(); expectedTypes = new ArrayList<>(); @@ -977,16 +905,20 @@ public class AppSearchImplTest { SchemaProto.newBuilder() .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Email")) + .setSchemaType("package$database1/Email") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Document")) + .setSchemaType("package$database1/Document") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database2/Email")) + .setSchemaType("package$database2/Email") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database2/Document")) + .setSchemaType("package$database2/Document") + .setVersion(0)) .build(); // Check Email and Document is saved in database 1 and 2 correctly. @@ -1012,13 +944,16 @@ public class AppSearchImplTest { SchemaProto.newBuilder() .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database1/Email")) + .setSchemaType("package$database1/Email") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database2/Email")) + .setSchemaType("package$database2/Email") + .setVersion(0)) .addTypes( SchemaTypeConfigProto.newBuilder() - .setSchemaType("package$database2/Document")) + .setSchemaType("package$database2/Document") + .setVersion(0)) .build(); // Check nothing changed in database2. @@ -1030,149 +965,6 @@ public class AppSearchImplTest { } @Test - public void testRemoveSchema_removedFromVisibilityStore() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "schema1", ImmutableList.of(package1)), - /*forceOverride=*/ false); - - // "schema1" is platform hidden now and package accessible - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema1")) - .isFalse(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema1", package1)) - .isTrue(); - - // Remove "schema1" by force overriding - mAppSearchImpl.setSchema( - "package", - "database", - /*schemas=*/ Collections.emptyList(), - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); - - // Check that "schema1" is no longer considered platform hidden or package accessible - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema1")) - .isTrue(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema1", package1)) - .isFalse(); - - // Add "schema1" back, it gets default visibility settings which means it's not platform - // hidden and not package accessible - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "schema1")) - .isTrue(); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "schema1", package1)) - .isFalse(); - } - - @Test - public void testSetSchema_defaultPlatformVisible() throws Exception { - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "Schema")) - .isTrue(); - } - - @Test - public void testSetSchema_platformHidden() throws Exception { - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPlatformSurfaceable(prefix, prefix + "Schema")) - .isFalse(); - } - - @Test - public void testSetSchema_defaultNotPackageAccessible() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "Schema", package1)) - .isFalse(); - } - - @Test - public void testSetSchema_packageAccessible() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - - String prefix = AppSearchImpl.createPrefix("package", "database"); - mAppSearchImpl.setSchema( - "package", - "database", - Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), - /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), - /*schemasPackageAccessible=*/ ImmutableMap.of("Schema", ImmutableList.of(package1)), - /*forceOverride=*/ false); - assertThat( - mAppSearchImpl - .getVisibilityStoreLocked() - .isSchemaPackageAccessible(prefix, prefix + "Schema", package1)) - .isTrue(); - } - - @Test public void testHasSchemaType() throws Exception { // Nothing exists yet assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isFalse(); @@ -1191,14 +983,12 @@ public class AppSearchImplTest { } @Test - public void testGetDatabases() throws Exception { - // No client databases exist yet, but the VisibilityStore's does - assertThat(mAppSearchImpl.getPrefixesLocked()) - .containsExactly( - AppSearchImpl.createPrefix( - VisibilityStore.PACKAGE_NAME, VisibilityStore.DATABASE_NAME)); + public void testGetPrefixes() throws Exception { + Set<String> existingPrefixes = mAppSearchImpl.getPrefixesLocked(); // Has database1 + Set<String> expectedPrefixes = new ArraySet<>(existingPrefixes); + expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database1")); mAppSearchImpl.setSchema( "package", "database1", @@ -1206,13 +996,10 @@ public class AppSearchImplTest { /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), /*forceOverride=*/ false); - assertThat(mAppSearchImpl.getPrefixesLocked()) - .containsExactly( - AppSearchImpl.createPrefix( - VisibilityStore.PACKAGE_NAME, VisibilityStore.DATABASE_NAME), - AppSearchImpl.createPrefix("package", "database1")); + assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); // Has both databases + expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database2")); mAppSearchImpl.setSchema( "package", "database2", @@ -1220,12 +1007,7 @@ public class AppSearchImplTest { /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), /*forceOverride=*/ false); - assertThat(mAppSearchImpl.getPrefixesLocked()) - .containsExactly( - AppSearchImpl.createPrefix( - VisibilityStore.PACKAGE_NAME, VisibilityStore.DATABASE_NAME), - AppSearchImpl.createPrefix("package", "database1"), - AppSearchImpl.createPrefix("package", "database2")); + assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); } @Test diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java deleted file mode 100644 index e491ac327280..000000000000 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.appsearch.external.localstorage; - -import static com.google.common.truth.Truth.assertThat; - -import android.app.appsearch.PackageIdentifier; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.Collections; - -public class VisibilityStoreTest { - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - private AppSearchImpl mAppSearchImpl; - private VisibilityStore mVisibilityStore; - - @Before - public void setUp() throws Exception { - mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder()); - mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked(); - } - - /** - * Make sure that we don't conflict with any special characters that AppSearchImpl has reserved. - */ - @Test - public void testValidPackageName() { - assertThat(VisibilityStore.PACKAGE_NAME) - .doesNotContain( - "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences - assertThat(VisibilityStore.PACKAGE_NAME) - .doesNotContain( - "" - + AppSearchImpl - .DATABASE_DELIMITER); // Convert the chars to CharSequences - } - - /** - * Make sure that we don't conflict with any special characters that AppSearchImpl has reserved. - */ - @Test - public void testValidDatabaseName() { - assertThat(VisibilityStore.DATABASE_NAME) - .doesNotContain( - "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences - assertThat(VisibilityStore.DATABASE_NAME) - .doesNotContain( - "" - + AppSearchImpl - .DATABASE_DELIMITER); // Convert the chars to CharSequences - } - - @Test - public void testSetVisibility_platformSurfaceable() throws Exception { - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of( - "prefix/schema1", "prefix/schema2"), - /*schemasPackageAccessible=*/ Collections.emptyMap()); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")) - .isFalse(); - - // New .setVisibility() call completely overrides previous visibility settings. So - // "schema2" isn't preserved. - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of( - "prefix/schema1", "prefix/schema3"), - /*schemasPackageAccessible=*/ Collections.emptyMap()); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3")) - .isFalse(); - - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), - /*schemasPackageAccessible=*/ Collections.emptyMap()); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3")) - .isTrue(); - } - - @Test - public void testSetVisibility_packageAccessible() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - PackageIdentifier package2 = - new PackageIdentifier("package2", /*sha256Certificate=*/ new byte[] {100}); - PackageIdentifier package3 = - new PackageIdentifier("package3", /*sha256Certificate=*/ new byte[] {100}); - - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "prefix/schema1", ImmutableList.of(package1), - "prefix/schema2", ImmutableList.of(package2))); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1)) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2)) - .isTrue(); - - // New .setVisibility() call completely overrides previous visibility settings. So - // "schema2" isn't preserved. - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "prefix/schema1", ImmutableList.of(package1), - "prefix/schema3", ImmutableList.of(package3))); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1)) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2)) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema3", package3)) - .isTrue(); - - mVisibilityStore.setVisibility( - "prefix", - /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(), - /*schemasPackageAccessible=*/ Collections.emptyMap()); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1)) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2)) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema3", package3)) - .isFalse(); - } - - @Test - public void testEmptyPrefix() throws Exception { - PackageIdentifier package1 = - new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100}); - PackageIdentifier package2 = - new PackageIdentifier("package2", /*sha256Certificate=*/ new byte[] {100}); - - mVisibilityStore.setVisibility( - /*prefix=*/ "", - /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"), - /*schemasPackageAccessible=*/ ImmutableMap.of( - "schema1", ImmutableList.of(package1), - "schema2", ImmutableList.of(package2))); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema1")) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema2")) - .isFalse(); - assertThat(mVisibilityStore.isSchemaPackageAccessible(/*prefix=*/ "", "schema1", package1)) - .isTrue(); - assertThat(mVisibilityStore.isSchemaPackageAccessible(/*prefix=*/ "", "schema2", package2)) - .isTrue(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java index 88edcb857aaf..dc225f11ae80 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java @@ -32,35 +32,35 @@ public class SchemaToProtoConverterTest { public void testGetProto_Email() { AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email") + .setVersion(12345) .addProperty( - new AppSearchSchema.PropertyConfig.Builder("subject") - .setDataType( - AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + new AppSearchSchema.StringPropertyConfig.Builder("subject") .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setIndexingType( - AppSearchSchema.PropertyConfig + AppSearchSchema.StringPropertyConfig .INDEXING_TYPE_PREFIXES) .setTokenizerType( - AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) + AppSearchSchema.StringPropertyConfig + .TOKENIZER_TYPE_PLAIN) .build()) .addProperty( - new AppSearchSchema.PropertyConfig.Builder("body") - .setDataType( - AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + new AppSearchSchema.StringPropertyConfig.Builder("body") .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) .setIndexingType( - AppSearchSchema.PropertyConfig + AppSearchSchema.StringPropertyConfig .INDEXING_TYPE_PREFIXES) .setTokenizerType( - AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) + AppSearchSchema.StringPropertyConfig + .TOKENIZER_TYPE_PLAIN) .build()) .build(); SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder() .setSchemaType("Email") + .setVersion(12345) .addProperties( PropertyConfigProto.newBuilder() .setPropertyName("subject") @@ -100,32 +100,27 @@ public class SchemaToProtoConverterTest { AppSearchSchema musicRecordingSchema = new AppSearchSchema.Builder("MusicRecording") .addProperty( - new AppSearchSchema.PropertyConfig.Builder("artist") - .setDataType( - AppSearchSchema.PropertyConfig.DATA_TYPE_STRING) + new AppSearchSchema.StringPropertyConfig.Builder("artist") .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) .setIndexingType( - AppSearchSchema.PropertyConfig + AppSearchSchema.StringPropertyConfig .INDEXING_TYPE_PREFIXES) .setTokenizerType( - AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN) + AppSearchSchema.StringPropertyConfig + .TOKENIZER_TYPE_PLAIN) .build()) .addProperty( - new AppSearchSchema.PropertyConfig.Builder("pubDate") - .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64) + new AppSearchSchema.Int64PropertyConfig.Builder("pubDate") .setCardinality( AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) - .setIndexingType( - AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE) - .setTokenizerType( - AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE) .build()) .build(); SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder() .setSchemaType("MusicRecording") + .setVersion(0) .addProperties( PropertyConfigProto.newBuilder() .setPropertyName("artist") @@ -144,14 +139,7 @@ public class SchemaToProtoConverterTest { .setPropertyName("pubDate") .setDataType(PropertyConfigProto.DataType.Code.INT64) .setCardinality( - PropertyConfigProto.Cardinality.Code.OPTIONAL) - .setStringIndexingConfig( - StringIndexingConfig.newBuilder() - .setTokenizerType( - StringIndexingConfig.TokenizerType - .Code.NONE) - .setTermMatchType( - TermMatchType.Code.UNKNOWN))) + PropertyConfigProto.Cardinality.Code.OPTIONAL)) .build(); assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(musicRecordingSchema)) diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index 4ecaac55ce96..79a5ed65b999 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -17,6 +17,7 @@ package com.android.server.audio; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -27,6 +28,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.Intent; import android.media.AudioManager; import android.media.AudioSystem; import android.util.Log; @@ -58,7 +60,7 @@ public class AudioDeviceBrokerTest { @Mock private AudioService mMockAudioService; @Spy private AudioDeviceInventory mSpyDevInventory; @Spy private AudioSystemAdapter mSpyAudioSystem; - private SystemServerAdapter mSystemServer; + @Spy private SystemServerAdapter mSpySystemServer; private BluetoothDevice mFakeBtDevice; @@ -69,9 +71,9 @@ public class AudioDeviceBrokerTest { mMockAudioService = mock(AudioService.class); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); - mSystemServer = new NoOpSystemServerAdapter(); + mSpySystemServer = spy(new NoOpSystemServerAdapter()); mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory, - mSystemServer); + mSpySystemServer); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -172,6 +174,30 @@ public class AudioDeviceBrokerTest { true); } + /** + * Test that device wired state intents are broadcasted on connection state change + * @throws Exception + */ + @Test + public void testSetWiredDeviceConnectionState() throws Exception { + Log.i(TAG, "starting postSetWiredDeviceConnectionState"); + + final String address = "testAddress"; + final String name = "testName"; + final String caller = "testCaller"; + + doNothing().when(mSpySystemServer).broadcastStickyIntentToCurrentProfileGroup( + any(Intent.class)); + + mSpyDevInventory.setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + AudioService.CONNECTION_STATE_CONNECTED, address, name, caller); + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + + // Verify that the sticky intent is broadcasted + verify(mSpySystemServer, times(1)).broadcastStickyIntentToCurrentProfileGroup( + any(Intent.class)); + } + private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection, boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception { when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC)) diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index cc4541b438e0..832f918c8044 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -34,11 +34,14 @@ import android.hardware.biometrics.IBiometricService; import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.proto.ProtoOutputStream; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.nano.BiometricSchedulerProto; +import com.android.server.biometrics.nano.BiometricsProto; import com.android.server.biometrics.sensors.BiometricScheduler.Operation; import org.junit.Before; @@ -52,6 +55,7 @@ public class BiometricSchedulerTest { private static final String TAG = "BiometricSchedulerTest"; private static final int TEST_SENSOR_ID = 1; + private static final int LOG_NUM_RECENT_OPERATIONS = 2; private BiometricScheduler mScheduler; private IBinder mToken; @@ -66,7 +70,7 @@ public class BiometricSchedulerTest { MockitoAnnotations.initMocks(this); mToken = new Binder(); mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */, - mBiometricService); + mBiometricService, LOG_NUM_RECENT_OPERATIONS); } @Test @@ -186,6 +190,88 @@ public class BiometricSchedulerTest { assertNull(mScheduler.mCurrentOperation); } + @Test + public void testProtoDump_singleCurrentOperation() throws Exception { + // Nothing so far + BiometricSchedulerProto bsp = getDump(true /* clearSchedulerBuffer */); + assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation); + assertEquals(0, bsp.totalOperations); + assertEquals(0, bsp.recentOperations.length); + + // Pretend the scheduler is busy enrolling, and check the proto dump again. + final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, + () -> mock(Object.class), BiometricsProto.CM_ENROLL); + mScheduler.scheduleClientMonitor(client); + waitForIdle(); + bsp = getDump(true /* clearSchedulerBuffer */); + assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation); + // No operations have completed yet + assertEquals(0, bsp.totalOperations); + assertEquals(0, bsp.recentOperations.length); + // Finish this operation, so the next scheduled one can start + client.getCallback().onClientFinished(client, true); + } + + @Test + public void testProtoDump_fifo() throws Exception { + // Add the first operation + final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, + () -> mock(Object.class), BiometricsProto.CM_ENROLL); + mScheduler.scheduleClientMonitor(client); + waitForIdle(); + BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */); + assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation); + // No operations have completed yet + assertEquals(0, bsp.totalOperations); + assertEquals(0, bsp.recentOperations.length); + // Finish this operation, so the next scheduled one can start + client.getCallback().onClientFinished(client, true); + + // Add another operation + final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken, + () -> mock(Object.class), BiometricsProto.CM_REMOVE); + mScheduler.scheduleClientMonitor(client2); + waitForIdle(); + bsp = getDump(false /* clearSchedulerBuffer */); + assertEquals(BiometricsProto.CM_REMOVE, bsp.currentOperation); + assertEquals(1, bsp.totalOperations); // Enroll finished + assertEquals(1, bsp.recentOperations.length); + assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]); + client2.getCallback().onClientFinished(client2, true); + + // And another operation + final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken, + () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE); + mScheduler.scheduleClientMonitor(client3); + waitForIdle(); + bsp = getDump(false /* clearSchedulerBuffer */); + assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.currentOperation); + assertEquals(2, bsp.totalOperations); + assertEquals(2, bsp.recentOperations.length); + assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]); + assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[1]); + + // Finish the last operation, and check that the first operation is removed from the FIFO. + // The test initializes the scheduler with "LOG_NUM_RECENT_OPERATIONS = 2" :) + client3.getCallback().onClientFinished(client3, true); + waitForIdle(); + bsp = getDump(true /* clearSchedulerBuffer */); + assertEquals(3, bsp.totalOperations); + assertEquals(2, bsp.recentOperations.length); + assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[0]); + assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.recentOperations[1]); + // Nothing is currently running anymore + assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation); + + // RecentOperations queue is cleared (by the previous dump) + bsp = getDump(true /* clearSchedulerBuffer */); + assertEquals(0, bsp.recentOperations.length); + } + + private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { + return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); + } + private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> { public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token, @@ -207,6 +293,21 @@ public class BiometricSchedulerTest { } } + private static class TestClientMonitor2 extends TestClientMonitor { + private final int mProtoEnum; + + public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token, + @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) { + super(context, token, lazyDaemon); + mProtoEnum = protoEnum; + } + + @Override + public int getProtoEnum() { + return mProtoEnum; + } + } + private static class TestClientMonitor extends HalClientMonitor<Object> { private boolean mUnableToStart; private boolean mStarted; @@ -230,6 +331,13 @@ public class BiometricSchedulerTest { } @Override + public int getProtoEnum() { + // Anything other than CM_NONE, which is used to represent "idle". Tests that need + // real proto enums should use TestClientMonitor2 + return BiometricsProto.CM_UPDATE_ACTIVE_USER; + } + + @Override public void start(@NonNull Callback callback) { super.start(callback); assertFalse(mStarted); 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 065a2f307f5b..2e97a8dfb839 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -31,6 +31,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.PasswordMetrics.computeForPassword; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; +import static android.net.InetAddresses.parseNumericAddress; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; @@ -66,6 +67,8 @@ import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.testng.Assert.assertThrows; +import static java.util.Collections.emptyList; + import android.Manifest.permission; import android.app.Activity; import android.app.AppOpsManager; @@ -126,6 +129,8 @@ import org.mockito.internal.util.collections.Sets; import org.mockito.stubbing.Answer; import java.io.File; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -2360,6 +2365,49 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testGetProxyParameters() throws Exception { + assertThat(dpm.getProxyParameters(inetAddrProxy("192.0.2.1", 1234), emptyList())) + .isEqualTo(new Pair<>("192.0.2.1:1234", "")); + assertThat(dpm.getProxyParameters(inetAddrProxy("192.0.2.1", 1234), + listOf("one.example.com ", " two.example.com "))) + .isEqualTo(new Pair<>("192.0.2.1:1234", "one.example.com,two.example.com")); + assertThat(dpm.getProxyParameters(hostnameProxy("proxy.example.com", 1234), emptyList())) + .isEqualTo(new Pair<>("proxy.example.com:1234", "")); + assertThat(dpm.getProxyParameters(hostnameProxy("proxy.example.com", 1234), + listOf("excluded.example.com"))) + .isEqualTo(new Pair<>("proxy.example.com:1234", "excluded.example.com")); + + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + inetAddrProxy("192.0.2.1", 0), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("", 1234), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("", 0), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("invalid! hostname", 1234), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", 1234), listOf("invalid exclusion"))); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", -1), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", 0xFFFF + 1), emptyList())); + } + + private static Proxy inetAddrProxy(String inetAddr, int port) { + return new Proxy( + Proxy.Type.HTTP, new InetSocketAddress(parseNumericAddress(inetAddr), port)); + } + + private static Proxy hostnameProxy(String hostname, int port) { + return new Proxy( + Proxy.Type.HTTP, InetSocketAddress.createUnresolved(hostname, port)); + } + + private static List<String> listOf(String... args) { + return Arrays.asList(args); + } + + @Test public void testSetKeyguardDisabledFeaturesWithDO() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); @@ -3289,7 +3337,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(false); initializeDpms(); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(true); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); @@ -3331,7 +3379,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(false); initializeDpms(); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(true); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); @@ -3395,7 +3443,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(true); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); @@ -3439,7 +3487,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(true); setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); @@ -3459,8 +3507,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308); when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false /* we can't remove a managed profile */)).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, - true)).thenReturn(true); } @Test @@ -3620,7 +3666,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(false); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); @@ -3664,7 +3710,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(false); setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); @@ -3711,7 +3757,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); when(getServices().userManager.canAddMoreManagedProfiles(CALLER_USER_HANDLE, - true)).thenReturn(true); + false)).thenReturn(true); setUserSetupCompleteForUser(false, CALLER_USER_HANDLE); mContext.binder.callingUid = DpmMockContext.CALLER_UID; @@ -3753,7 +3799,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); when(getServices().userManager.canAddMoreManagedProfiles(CALLER_USER_HANDLE, - true)).thenReturn(true); + false)).thenReturn(true); setUserSetupCompleteForUser(true, CALLER_USER_HANDLE); mContext.binder.callingUid = DpmMockContext.CALLER_UID; @@ -3800,7 +3846,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager .hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)).thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); - when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false)) .thenReturn(false); setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); @@ -3835,7 +3881,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.getProfileParent(CALLER_USER_HANDLE)) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(getServices().userManager.canAddMoreManagedProfiles(CALLER_USER_HANDLE, - true)).thenReturn(true); + false)).thenReturn(true); setUserSetupCompleteForUser(false, CALLER_USER_HANDLE); mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; @@ -3861,7 +3907,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); } - private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception { + private void setup_provisionManagedProfileOneAlreadyExist_primaryUser() throws Exception { setDeviceOwner(); when(getServices().ipackageManager @@ -3873,26 +3919,24 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(true); when(getServices().userManager.canAddMoreManagedProfiles(CALLER_USER_HANDLE, false /* we can't remove a managed profile */)).thenReturn(false); - when(getServices().userManager.canAddMoreManagedProfiles(CALLER_USER_HANDLE, - true)).thenReturn(true); setUserSetupCompleteForUser(false, CALLER_USER_HANDLE); mContext.binder.callingUid = DpmMockContext.CALLER_UID; } @Test - public void testIsProvisioningAllowed_provisionManagedProfileCantRemoveUser_primaryUser() + public void testIsProvisioningAllowed_provisionManagedProfile_oneAlreadyExists_primaryUser() throws Exception { - setup_provisionManagedProfileCantRemoveUser_primaryUser(); + setup_provisionManagedProfileOneAlreadyExist_primaryUser(); mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } @Test - public void testCheckProvisioningPreCondition_provisionManagedProfileCantRemoveUser_primaryUser() + public void testCheckProvisioningPreCondition_provisionManagedProfile_oneAlreadyExists_primaryUser() throws Exception { - setup_provisionManagedProfileCantRemoveUser_primaryUser(); + setup_provisionManagedProfileOneAlreadyExist_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); @@ -5494,7 +5538,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Attempt to set to empty list (which means no listener is allowlisted) mContext.binder.callingUid = adminUid; assertThat(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())).isFalse(); + admin1, emptyList())).isFalse(); assertThat(dpms.getPermittedCrossProfileNotificationListeners(admin1)).isNull(); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; @@ -5588,7 +5632,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Setting an empty allowlist - only system listeners allowed mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; assertThat(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())).isTrue(); + admin1, emptyList())).isTrue(); assertThat(dpms.getPermittedCrossProfileNotificationListeners(admin1).size()).isEqualTo(0); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; @@ -5653,7 +5697,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // all allowed in primary profile mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; assertThat(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())).isTrue(); + admin1, emptyList())).isTrue(); assertThat(dpms.getPermittedCrossProfileNotificationListeners(admin1).size()).isEqualTo(0); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; 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 0cf0af39dc1e..75bf1e6bd485 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 @@ -19,6 +19,8 @@ package com.android.server.graphics.fonts; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + import android.content.Context; import android.os.FileUtils; import android.platform.test.annotations.Presubmit; @@ -35,7 +37,12 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; @Presubmit @SmallTest @@ -44,17 +51,63 @@ public final class UpdatableFontDirTest { /** * A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files, - * this test uses fake font files. A fake font file has its version as its file content. + * this test uses fake font files. A fake font file has its PostScript naem and revision as the + * file content. */ private static class FakeFontFileParser implements UpdatableFontDir.FontFileParser { @Override - public long getVersion(File file) throws IOException { - return Long.parseLong(FileUtils.readTextFile(file, 100, "")); + public String getPostScriptName(File file) throws IOException { + String content = FileUtils.readTextFile(file, 100, ""); + return content.split(",")[0]; + } + + @Override + public long getRevision(File file) throws IOException { + String content = FileUtils.readTextFile(file, 100, ""); + return Long.parseLong(content.split(",")[1]); + } + } + + // FakeFsverityUtil will successfully set up fake fs-verity if the signature is GOOD_SIGNATURE. + private static final String GOOD_SIGNATURE = "Good signature"; + + /** A fake FsverityUtil to keep fake verity bit in memory. */ + private static class FakeFsverityUtil implements UpdatableFontDir.FsverityUtil { + private final Set<String> mHasFsverityPaths = new HashSet<>(); + + public void remove(String name) { + mHasFsverityPaths.remove(name); + } + + @Override + public boolean hasFsverity(String path) { + return mHasFsverityPaths.contains(path); + } + + @Override + public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException { + String fakeSignature = new String(pkcs7Signature, StandardCharsets.UTF_8); + if (GOOD_SIGNATURE.equals(fakeSignature)) { + mHasFsverityPaths.add(path); + } else { + throw new IOException("Failed to set up fake fs-verity"); + } + } + + @Override + public boolean rename(File src, File dest) { + if (src.renameTo(dest)) { + mHasFsverityPaths.remove(src.getAbsolutePath()); + mHasFsverityPaths.add(dest.getAbsolutePath()); + return true; + } + return false; } } private File mCacheDir; private File mUpdatableFontFilesDir; + private List<File> mPreinstalledFontDirs; @SuppressWarnings("ResultOfMethodCallIgnored") @Before @@ -65,6 +118,12 @@ public final class UpdatableFontDirTest { mCacheDir.mkdirs(); mUpdatableFontFilesDir = new File(mCacheDir, "updatable_fonts"); mUpdatableFontFilesDir.mkdir(); + mPreinstalledFontDirs = new ArrayList<>(); + mPreinstalledFontDirs.add(new File(mCacheDir, "system_fonts")); + mPreinstalledFontDirs.add(new File(mCacheDir, "product_fonts")); + for (File dir : mPreinstalledFontDirs) { + dir.mkdir(); + } } @After @@ -75,19 +134,22 @@ public final class UpdatableFontDirTest { @Test public void construct() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dirForPreparation = new UpdatableFontDir(mUpdatableFontFilesDir, parser); - installFontFile(dirForPreparation, "foo.ttf", "1"); - installFontFile(dirForPreparation, "bar.ttf", "2"); - installFontFile(dirForPreparation, "foo.ttf", "3"); - installFontFile(dirForPreparation, "bar.ttf", "4"); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dirForPreparation = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3); + assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4); + assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4); // Outdated font dir should be deleted. assertThat(mUpdatableFontFilesDir.list()).hasLength(2); } @@ -95,66 +157,189 @@ public final class UpdatableFontDirTest { @Test public void construct_empty() { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test + public void construct_missingFsverity() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dirForPreparation = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + // Four font dirs are created. + assertThat(mUpdatableFontFilesDir.list()).hasLength(4); + + fakeFsverityUtil.remove( + dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath()); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + assertThat(dir.getFontFileMap()).isEmpty(); + // All font dirs (including dir for "bar.ttf") should be deleted. + assertThat(mUpdatableFontFilesDir.list()).hasLength(0); + } + + @Test + public void construct_fontNameMismatch() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dirForPreparation = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + // Four font dirs are created. + assertThat(mUpdatableFontFilesDir.list()).hasLength(4); + + // Overwrite "foo.ttf" with wrong contents. + FileUtils.stringToFile(dirForPreparation.getFontFileMap().get("foo.ttf"), "bar,4"); + + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); assertThat(dir.getFontFileMap()).isEmpty(); + // All font dirs (including dir for "bar.ttf") should be deleted. + assertThat(mUpdatableFontFilesDir.list()).hasLength(0); + } + + @Test + public void construct_olderThanPreinstalledFont() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dirForPreparation = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); + installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + // Four font dirs are created. + assertThat(mUpdatableFontFilesDir.list()).hasLength(4); + + // Add preinstalled fonts. + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "foo.ttf"), "foo,5"); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "bar.ttf"), "bar,1"); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2"); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + // For foo.ttf, preinstalled font (revision 5) should be used. + assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf"); + // For bar.ttf, updated font (revision 4) should be used. + assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); + assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4); + // Outdated font dir should be deleted. + // We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled + // fonts. + assertThat(mUpdatableFontFilesDir.list()).hasLength(1); } @Test public void installFontFile() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); - installFontFile(dir, "test.ttf", "1"); + installFontFile(dir, "test,1", GOOD_SIGNATURE); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1); + assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1); } @Test public void installFontFile_upgrade() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); - installFontFile(dir, "test.ttf", "1"); + installFontFile(dir, "test,1", GOOD_SIGNATURE); Map<String, File> mapBeforeUpgrade = dir.getFontFileMap(); - installFontFile(dir, "test.ttf", "2"); + installFontFile(dir, "test,2", GOOD_SIGNATURE); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); + assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); assertThat(mapBeforeUpgrade).containsKey("test.ttf"); assertWithMessage("Older fonts should not be deleted until next loadFontFileMap") - .that(parser.getVersion(mapBeforeUpgrade.get("test.ttf"))).isEqualTo(1); + .that(parser.getRevision(mapBeforeUpgrade.get("test.ttf"))).isEqualTo(1); } @Test public void installFontFile_downgrade() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); - installFontFile(dir, "test.ttf", "2"); - installFontFile(dir, "test.ttf", "1"); + installFontFile(dir, "test,2", GOOD_SIGNATURE); + try { + installFontFile(dir, "test,1", GOOD_SIGNATURE); + fail("Expect IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expect + } assertThat(dir.getFontFileMap()).containsKey("test.ttf"); - assertWithMessage("Font should not be downgraded to an older version") - .that(parser.getVersion(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); + assertWithMessage("Font should not be downgraded to an older revision") + .that(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); } @Test public void installFontFile_multiple() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); - UpdatableFontDir dir = new UpdatableFontDir(mUpdatableFontFilesDir, parser); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); - installFontFile(dir, "foo.ttf", "1"); - installFontFile(dir, "bar.ttf", "2"); + installFontFile(dir, "foo,1", GOOD_SIGNATURE); + installFontFile(dir, "bar,2", GOOD_SIGNATURE); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); + assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); - assertThat(parser.getVersion(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2); + assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2); + } + + @Test + public void installFontFile_invalidSignature() { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + + try { + installFontFile(dir, "test,1", "Invalid signature"); + fail("Expect IOException"); + } catch (IOException e) { + // Expect + } + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test + public void installFontFile_olderThanPreinstalledFont() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1"); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + + try { + installFontFile(dir, "test,1", GOOD_SIGNATURE); + fail("Expect IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expect + } + assertThat(dir.getFontFileMap()).isEmpty(); } - private void installFontFile(UpdatableFontDir dir, String name, String content) + private void installFontFile(UpdatableFontDir dir, String content, String signature) throws IOException { - File file = File.createTempFile(name, "", mCacheDir); + File file = File.createTempFile("font", "ttf", mCacheDir); FileUtils.stringToFile(file, content); try (FileInputStream in = new FileInputStream(file)) { - dir.installFontFile(name, in.getFD()); + dir.installFontFile(in.getFD(), signature.getBytes()); } } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java new file mode 100644 index 000000000000..b9af82b64c02 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.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 com.android.server.locksettings; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.service.resumeonreboot.ResumeOnRebootService; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@SmallTest +@RunWith(JUnit4.class) +public class ResumeOnRebootServiceProviderTests { + + @Mock + Context mMockContext; + @Mock + PackageManager mMockPackageManager; + @Mock + ResolveInfo mMockResolvedInfo; + @Mock + ServiceInfo mMockServiceInfo; + @Mock + ComponentName mMockComponentName; + @Captor + ArgumentCaptor<Intent> mIntentArgumentCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getUserId()).thenReturn(0); + when(mMockResolvedInfo.serviceInfo).thenReturn(mMockServiceInfo); + when(mMockServiceInfo.getComponentName()).thenReturn(mMockComponentName); + } + + @Test + public void noServiceFound() throws Exception { + when(mMockPackageManager.queryIntentServices(any(), + eq(PackageManager.MATCH_SYSTEM_ONLY))).thenReturn( + null); + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNull(); + } + + @Test + public void serviceNotGuardedWithPermission() throws Exception { + ArrayList<ResolveInfo> resultList = new ArrayList<>(); + when(mMockServiceInfo.permission).thenReturn(""); + resultList.add(mMockResolvedInfo); + when(mMockPackageManager.queryIntentServices(any(), any())).thenReturn( + resultList); + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNull(); + } + + @Test + public void serviceResolved() throws Exception { + ArrayList<ResolveInfo> resultList = new ArrayList<>(); + resultList.add(mMockResolvedInfo); + when(mMockServiceInfo.permission).thenReturn( + Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE); + when(mMockPackageManager.queryIntentServices(any(), + eq(PackageManager.MATCH_SYSTEM_ONLY))).thenReturn( + resultList); + + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNotNull(); + + verify(mMockPackageManager).queryIntentServices(mIntentArgumentCaptor.capture(), + eq(PackageManager.MATCH_SYSTEM_ONLY)); + assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo( + ResumeOnRebootService.SERVICE_INTERFACE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index 391611b72dab..5468fba59c10 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -78,7 +78,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testImmutableEnabledChange() { + public void testImmutableEnabledChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -106,7 +106,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutableEnabledChangeHasNoEffect() { + public void testMutableEnabledChangeHasNoEffect() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -134,7 +134,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutableEnabledToImmutableEnabled() { + public void testMutableEnabledToImmutableEnabled() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -178,7 +178,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testMutablePriorityChange() { + public void testMutablePriorityChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); @@ -218,7 +218,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI } @Test - public void testImmutablePriorityChange() { + public void testImmutablePriorityChange() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java index 4f882ce13dd4..33dbcc0855be 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -22,11 +22,14 @@ import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.os.OverlayablePolicy.CONFIG_SIGNATURE; 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.Assert.assertThrows; import android.content.om.OverlayInfo; +import android.util.Pair; import androidx.test.runner.AndroidJUnit4; @@ -35,6 +38,7 @@ import org.junit.runner.RunWith; import java.util.List; import java.util.Map; +import java.util.Optional; @RunWith(AndroidJUnit4.class) public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase { @@ -55,7 +59,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes private static final String CERT_CONFIG_NOK = "config_certificate_nok"; @Test - public void testGetOverlayInfo() { + public void testGetOverlayInfo() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); final OverlayManagerServiceImpl impl = getImpl(); @@ -67,7 +71,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForTarget() { + public void testGetOverlayInfosForTarget() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); installNewPackage(overlay(OVERLAY3, TARGET), USER2); @@ -92,7 +96,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForUser() { + public void testGetOverlayInfosForUser() throws Exception { installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); @@ -119,7 +123,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testPriority() { + public void testPriority() throws Exception { installNewPackage(overlay(OVERLAY, TARGET), USER); installNewPackage(overlay(OVERLAY2, TARGET), USER); installNewPackage(overlay(OVERLAY3, TARGET), USER); @@ -131,18 +135,21 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); - assertTrue(impl.setLowestPriority(OVERLAY3, USER)); + assertEquals(impl.setLowestPriority(OVERLAY3, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2); - assertTrue(impl.setHighestPriority(OVERLAY3, USER)); + assertEquals(impl.setHighestPriority(OVERLAY3, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); - assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER)); + assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3); } @Test - public void testOverlayInfoStateTransitions() { + public void testOverlayInfoStateTransitions() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); assertNull(impl.getOverlayInfo(OVERLAY, USER)); @@ -153,7 +160,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes installNewPackage(target, USER); assertState(STATE_DISABLED, OVERLAY, USER); - impl.setEnabled(OVERLAY, true, USER); + assertEquals(impl.setEnabled(OVERLAY, true, USER), + Optional.of(new PackageAndUser(TARGET, USER))); assertState(STATE_ENABLED, OVERLAY, USER); // target upgrades do not change the state of the overlay @@ -168,50 +176,40 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testOnOverlayPackageUpgraded() { - final FakeListener listener = getListener(); + public void testOnOverlayPackageUpgraded() throws Exception { final FakeDeviceState.PackageBuilder target = target(TARGET); final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET); installNewPackage(target, USER); installNewPackage(overlay, USER); - listener.count = 0; upgradePackage(overlay, USER); - assertEquals(2, listener.count); // upgrade to a version where the overlay has changed its target - // expect once for the old target package, once for the new target package - listener.count = 0; final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target"); - upgradePackage(overlay2, USER); - assertEquals(3, listener.count); - - listener.count = 0; - upgradePackage(overlay2, USER); - assertEquals(2, listener.count); + final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair = + upgradePackage(overlay2, USER); + assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER))); + assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER))); } @Test - public void testListener() { + public void testSetEnabledAtVariousConditions() throws Exception { final OverlayManagerServiceImpl impl = getImpl(); - final FakeListener listener = getListener(); - installNewPackage(overlay(OVERLAY, TARGET), USER); - assertEquals(1, listener.count); - listener.count = 0; + assertThrows(OverlayManagerServiceImpl.OperationFailedException.class, + () -> impl.setEnabled(OVERLAY, true, USER)); + // request succeeded, and there was a change that needs to be + // propagated to the rest of the system installNewPackage(target(TARGET), USER); - assertEquals(1, listener.count); - listener.count = 0; - - impl.setEnabled(OVERLAY, true, USER); - assertEquals(1, listener.count); - listener.count = 0; + installNewPackage(overlay(OVERLAY, TARGET), USER); + assertEquals(impl.setEnabled(OVERLAY, true, USER), + Optional.of(new PackageAndUser(TARGET, USER))); - impl.setEnabled(OVERLAY, true, USER); - assertEquals(0, listener.count); + // request succeeded, but nothing changed + assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent()); } @Test - public void testConfigSignaturePolicyOk() { + public void testConfigSignaturePolicyOk() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); @@ -229,7 +227,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyCertNok() { + public void testConfigSignaturePolicyCertNok() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); @@ -247,7 +245,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyNoConfig() { + public void testConfigSignaturePolicyNoConfig() throws Exception { addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER); @@ -262,7 +260,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyNoRefPkg() { + public void testConfigSignaturePolicyNoRefPkg() throws Exception { installNewPackage(target(TARGET), USER); installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER); @@ -276,7 +274,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testConfigSignaturePolicyRefPkgNotSystem() { + public void testConfigSignaturePolicyRefPkgNotSystem() throws Exception { setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG); reinitializeImpl(); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 006dda0f80e3..2c477c897b30 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -16,6 +16,8 @@ package com.android.server.om; +import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -30,6 +32,7 @@ import android.content.pm.PackageInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import androidx.annotation.Nullable; @@ -43,13 +46,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */ class OverlayManagerServiceImplTestsBase { private OverlayManagerServiceImpl mImpl; private FakeDeviceState mState; - private FakeListener mListener; private FakePackageManagerHelper mPackageManager; private FakeIdmapDaemon mIdmapDaemon; private OverlayConfig mOverlayConfig; @@ -58,7 +61,6 @@ class OverlayManagerServiceImplTestsBase { @Before public void setUp() { mState = new FakeDeviceState(); - mListener = new FakeListener(); mPackageManager = new FakePackageManagerHelper(mState); mIdmapDaemon = new FakeIdmapDaemon(mState); mOverlayConfig = mock(OverlayConfig.class); @@ -73,18 +75,13 @@ class OverlayManagerServiceImplTestsBase { new IdmapManager(mIdmapDaemon, mPackageManager), new OverlayManagerSettings(), mOverlayConfig, - new String[0], - mListener); + new String[0]); } OverlayManagerServiceImpl getImpl() { return mImpl; } - FakeListener getListener() { - return mListener; - } - FakeIdmapDaemon getIdmapd() { return mIdmapDaemon; } @@ -155,7 +152,8 @@ class OverlayManagerServiceImplTestsBase { * * @throws IllegalStateException if the package is currently installed */ - void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId) { + void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId) + throws OperationFailedException { if (mState.select(pkg.packageName, userId) != null) { throw new IllegalStateException("package " + pkg.packageName + " already installed"); } @@ -176,23 +174,30 @@ class OverlayManagerServiceImplTestsBase { * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the * {@link android.content.Intent#EXTRA_REPLACING} extra. * + * @return the two Optional<PackageAndUser> objects from starting and finishing the upgrade + * * @throws IllegalStateException if the package is not currently installed */ - void upgradePackage(FakeDeviceState.PackageBuilder pkg, int userId) { + Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage( + FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException { final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId); if (replacedPackage == null) { throw new IllegalStateException("package " + pkg.packageName + " not installed"); } + Optional<PackageAndUser> opt1 = Optional.empty(); if (replacedPackage.targetPackageName != null) { - mImpl.onOverlayPackageReplacing(pkg.packageName, userId); + opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId); } mState.add(pkg, userId); + Optional<PackageAndUser> opt2; if (pkg.targetPackage == null) { - mImpl.onTargetPackageReplaced(pkg.packageName, userId); + opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId); } else { - mImpl.onOverlayPackageReplaced(pkg.packageName, userId); + opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId); } + + return Pair.create(opt1, opt2); } /** @@ -203,7 +208,7 @@ class OverlayManagerServiceImplTestsBase { * * @throws IllegalStateException if the package is not currently installed */ - void uninstallPackage(String packageName, int userId) { + void uninstallPackage(String packageName, int userId) throws OperationFailedException { final FakeDeviceState.Package pkg = mState.select(packageName, userId); if (pkg == null) { throw new IllegalStateException("package " + packageName+ " not installed"); @@ -485,12 +490,4 @@ class OverlayManagerServiceImplTestsBase { } } } - - static class FakeListener implements OverlayManagerServiceImpl.OverlayChangeListener { - public int count; - - public void onOverlaysChanged(@NonNull String targetPackage, int userId) { - count++; - } - } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index b190339b129b..395b643e3777 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -219,8 +219,8 @@ public final class UserManagerTest { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true, asHandle(currentUser)); try { - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( - UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, + /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false, asHandle(currentUser)); @@ -232,9 +232,32 @@ public final class UserManagerTest { @MediumTest @Test + public void testRemoveUserOrSetEphemeral_evenWhenRestricted() throws Exception { + final int currentUser = ActivityManager.getCurrentUser(); + final UserInfo user1 = createUser("User 1", /* flags= */ 0); + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true, + asHandle(currentUser)); + try { + synchronized (mUserRemoveLock) { + assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, + /* evenWhenDisallowed= */ true)) + .isEqualTo(UserManager.REMOVE_RESULT_REMOVED); + waitForUserRemovalLocked(user1.id); + } + + } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false, + asHandle(currentUser)); + } + + assertThat(hasUser(user1.id)).isFalse(); + } + + @MediumTest + @Test public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception { - assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM)).isEqualTo( - UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM, + /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @@ -243,8 +266,8 @@ public final class UserManagerTest { @Test public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception { assertThat(hasUser(Integer.MAX_VALUE)).isFalse(); - assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE)).isEqualTo( - UserManager.REMOVE_RESULT_ERROR); + assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE, + /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR); } @MediumTest @@ -255,8 +278,8 @@ public final class UserManagerTest { // Switch to the user just created. switchUser(user1.id, null, /* ignoreHandle= */ true); - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( - UserManager.REMOVE_RESULT_SET_EPHEMERAL); + assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, /* evenWhenDisallowed= */ false)) + .isEqualTo(UserManager.REMOVE_RESULT_SET_EPHEMERAL); assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isTrue(); @@ -276,8 +299,8 @@ public final class UserManagerTest { public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception { final UserInfo user1 = createUser("User 1", /* flags= */ 0); synchronized (mUserRemoveLock) { - assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( - UserManager.REMOVE_RESULT_REMOVED); + assertThat(mUserManager.removeUserOrSetEphemeral(user1.id, + /* evenWhenDisallowed= */ false)).isEqualTo(UserManager.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(user1.id); } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 7f35511236f7..f07197cf77f6 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -77,6 +77,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.display.DisplayGroup; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.BatteryReceiver; @@ -97,9 +98,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * Tests for {@link com.android.server.power.PowerManagerService}. @@ -178,7 +182,8 @@ public class PowerManagerServiceTest { .thenReturn(mPowerSaveState); when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false); when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); - when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true); + when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), + anyBoolean())).thenReturn(true); when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); @@ -399,30 +404,33 @@ public class PowerManagerServiceTest { @Test public void testGetDesiredScreenPolicy_WithVR() throws Exception { createService(); + mService.systemReady(null); // Brighten up the screen - mService.setWakefulnessLocked(WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_UNKNOWN, 0); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null, + null); + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_BRIGHT); // Move to VR mService.setVrModeEnabled(true); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_VR); // Then take a nap - mService.setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, - 0); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null, + null); + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_OFF); // Wake up to VR - mService.setWakefulnessLocked(WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_UNKNOWN, 0); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null, + null); + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_VR); // And back to normal mService.setVrModeEnabled(false); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_BRIGHT); } @@ -673,8 +681,9 @@ public class PowerManagerServiceTest { } @Test - public void testForceSuspend_forceSuspendFailurePropogated() { + public void testForceSuspend_forceSuspendFailurePropagated() throws Exception { createService(); + startSystem(); when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false); assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse(); } @@ -838,7 +847,7 @@ public class PowerManagerServiceTest { createService(); startSystem(); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_BRIGHT); } @@ -857,11 +866,8 @@ public class PowerManagerServiceTest { public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() throws Exception { when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1"); createService(); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( - DisplayPowerRequest.POLICY_OFF); - startSystem(); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_OFF); } @@ -871,7 +877,7 @@ public class PowerManagerServiceTest { createService(); startSystem(); forceAwake(); - assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( + assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo( DisplayPowerRequest.POLICY_BRIGHT); } @@ -1143,4 +1149,85 @@ public class PowerManagerServiceTest { assertFalse( mService.getBinderServiceInstance().setPowerModeChecked(Mode.INTERACTIVE, false)); } + + @Test + public void testMultiDisplay_wakefulnessUpdates() throws Exception { + final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + + createService(); + startSystem(); + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null, + null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, + null, null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); + + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null, + null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + + @Test + public void testMultiDisplay_addDisplayGroup_preservesWakefulness() throws Exception { + final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + + createService(); + startSystem(); + + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null, + null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); + + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); + } + + @Test + public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() throws Exception { + final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + + createService(); + startSystem(); + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null, + null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); + + mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null, + null); + assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } } diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java index b07b8fa059d1..9b8a2a82c6df 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.IntentSender; import android.content.pm.PackageManager; +import android.hardware.boot.V1_2.IBootControl; import android.os.Handler; import android.os.IPowerManager; import android.os.IRecoverySystemProgressListener; @@ -68,12 +69,13 @@ public class RecoverySystemServiceTest { private IThermalService mIThermalService; private FileWriter mUncryptUpdateFileWriter; private LockSettingsInternal mLockSettingsInternal; + private IBootControl mIBootControl; private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package"; private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package"; @Before - public void setup() { + public void setup() throws Exception { mContext = mock(Context.class); mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties(); mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class); @@ -88,8 +90,13 @@ public class RecoverySystemServiceTest { PowerManager powerManager = new PowerManager(mock(Context.class), mIPowerManager, mIThermalService, new Handler(looper)); + mIBootControl = mock(IBootControl.class); + when(mIBootControl.getCurrentSlot()).thenReturn(0); + when(mIBootControl.getActiveBootSlot()).thenReturn(1); + mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties, - powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal); + powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal, + mIBootControl); } @Test @@ -332,6 +339,15 @@ public class RecoverySystemServiceTest { verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); } + + @Test + public void rebootWithLskf_slotMismatch_Failure() throws Exception { + assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true)); + mRecoverySystemService.onPreparedForReboot(true); + assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", false), + is(false)); + } + @Test public void rebootWithLskf_withoutPrepare_Failure() throws Exception { assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true), diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java index 131e4f321a6c..0727e5adb9ca 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java @@ -17,6 +17,7 @@ package com.android.server.recoverysystem; import android.content.Context; +import android.hardware.boot.V1_2.IBootControl; import android.os.PowerManager; import com.android.internal.widget.LockSettingsInternal; @@ -30,16 +31,19 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { private final FileWriter mUncryptPackageFileWriter; private final UncryptSocket mUncryptSocket; private final LockSettingsInternal mLockSettingsInternal; + private final IBootControl mIBootControl; MockInjector(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, - UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) { + UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, + IBootControl bootControl) { super(context); mSystemProperties = systemProperties; mPowerManager = powerManager; mUncryptPackageFileWriter = uncryptPackageFileWriter; mUncryptSocket = uncryptSocket; mLockSettingsInternal = lockSettingsInternal; + mIBootControl = bootControl; } @Override @@ -85,13 +89,19 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { public LockSettingsInternal getLockSettingsService() { return mLockSettingsInternal; } + + @Override + public IBootControl getBootControl() { + return mIBootControl; + } } RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, - UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) { + UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, + IBootControl bootControl) { super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter, - uncryptSocket, lockSettingsInternal)); + uncryptSocket, lockSettingsInternal, bootControl)); } public static class FakeSystemProperties { @@ -102,6 +112,8 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { || RecoverySystemService.INIT_SERVICE_SETUP_BCB.equals(key) || RecoverySystemService.INIT_SERVICE_CLEAR_BCB.equals(key)) { return null; + } else if (RecoverySystemService.AB_UPDATE.equals(key)) { + return "true"; } else { throw new IllegalArgumentException("unexpected test key: " + key); } diff --git a/services/tests/servicestests/src/com/android/server/textservices/OWNERS b/services/tests/servicestests/src/com/android/server/textservices/OWNERS new file mode 100644 index 000000000000..0471e29a25cd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/textservices/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 816455 + +include /services/core/java/com/android/server/textservices/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java new file mode 100644 index 000000000000..8ac6dfb6d960 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java @@ -0,0 +1,162 @@ +/* + * 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.timedetector; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlarmManager; +import android.app.timedetector.GnssTimeSuggestion; +import android.app.timedetector.TimeDetector; +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.LocationRequest; +import android.location.LocationTime; +import android.os.TimestampedValue; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public final class GnssTimeUpdateServiceTest { + private static final long GNSS_TIME = 999_999_999L; + private static final long ELAPSED_REALTIME_NS = 123_000_000L; + private static final long ELAPSED_REALTIME_MS = ELAPSED_REALTIME_NS / 1_000_000L; + + @Mock private Context mMockContext; + @Mock private TimeDetector mMockTimeDetector; + @Mock private AlarmManager mMockAlarmManager; + @Mock private LocationManager mMockLocationManager; + @Mock private LocationManagerInternal mLocationManagerInternal; + + private GnssTimeUpdateService mGnssTimeUpdateService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mMockContext.createAttributionContext(anyString())) + .thenReturn(mMockContext); + + when(mMockContext.getSystemServiceName(TimeDetector.class)) + .thenReturn((TimeDetector.class).getSimpleName()); + when(mMockContext.getSystemService(TimeDetector.class)) + .thenReturn(mMockTimeDetector); + + when(mMockContext.getSystemServiceName(LocationManager.class)) + .thenReturn((LocationManager.class).getSimpleName()); + when(mMockContext.getSystemService(LocationManager.class)) + .thenReturn(mMockLocationManager); + + when(mMockContext.getSystemServiceName(AlarmManager.class)) + .thenReturn((AlarmManager.class).getSimpleName()); + when(mMockContext.getSystemService(AlarmManager.class)) + .thenReturn(mMockAlarmManager); + + LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal); + + mGnssTimeUpdateService = + new GnssTimeUpdateService(mMockContext); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testLocationListenerOnLocationChanged_validLocationTime_suggestsGnssTime() { + TimestampedValue<Long> timeSignal = new TimestampedValue<>( + ELAPSED_REALTIME_MS, GNSS_TIME); + GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); + LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS); + doReturn(locationTime).when(mLocationManagerInternal).getGnssTimeMillis(); + + mGnssTimeUpdateService.requestGnssTimeUpdates(); + + ArgumentCaptor<LocationListener> argumentCaptor = + ArgumentCaptor.forClass(LocationListener.class); + verify(mMockLocationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL) + .setMinUpdateIntervalMillis(0) + .build()), + any(), + argumentCaptor.capture()); + LocationListener locationListener = argumentCaptor.getValue(); + Location location = new Location(LocationManager.GPS_PROVIDER); + + locationListener.onLocationChanged(location); + + verify(mMockLocationManager).removeUpdates(locationListener); + verify(mMockTimeDetector).suggestGnssTime(timeSuggestion); + verify(mMockAlarmManager).set( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + anyLong(), + any(), + any(), + any()); + } + + @Test + public void testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime() { + doReturn(null).when(mLocationManagerInternal).getGnssTimeMillis(); + + mGnssTimeUpdateService.requestGnssTimeUpdates(); + + ArgumentCaptor<LocationListener> argumentCaptor = + ArgumentCaptor.forClass(LocationListener.class); + verify(mMockLocationManager).requestLocationUpdates( + eq(LocationManager.GPS_PROVIDER), + eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL) + .setMinUpdateIntervalMillis(0) + .build()), + any(), + argumentCaptor.capture()); + LocationListener locationListener = argumentCaptor.getValue(); + Location location = new Location(LocationManager.GPS_PROVIDER); + + locationListener.onLocationChanged(location); + + verify(mMockLocationManager).removeUpdates(locationListener); + verify(mMockTimeDetector, never()).suggestGnssTime(any()); + verify(mMockAlarmManager).set( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + anyLong(), + any(), + any(), + any()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index bee739231d3f..0fce4baf966f 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -213,6 +213,60 @@ public class VibrationThreadTest { } @Test + public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately() + throws Exception { + mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + + long vibrationId = 1; + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .compose(); + VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect); + + Thread.sleep(20); + assertTrue(vibrationThread.isAlive()); + assertTrue(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); + + // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should + // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. + Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); + cancellingThread.start(); + + waitForCompletion(vibrationThread, 20); + waitForCompletion(cancellingThread); + + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); + assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); + } + + @Test + public void vibrate_singleVibratorWaveformCancel_cancelsVibrationImmediately() + throws Exception { + mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + + long vibrationId = 1; + VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0); + VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect); + + Thread.sleep(20); + assertTrue(vibrationThread.isAlive()); + assertTrue(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); + + // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should + // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. + Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); + cancellingThread.start(); + + waitForCompletion(vibrationThread, 20); + waitForCompletion(cancellingThread); + + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); + assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); + } + + @Test public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception { mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD); @@ -544,36 +598,70 @@ public class VibrationThreadTest { } @Test - public void vibrate_multipleCancelled_allVibratorsStopped() throws Exception { - mockVibrators(1, 2, 3); + public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception { + mockVibrators(1, 2); + mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); + mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + + long vibrationId = 1; + CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() + .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addVibrator(2, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) + .compose()) + .combine(); + VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect); + + Thread.sleep(10); + assertTrue(vibrationThread.isAlive()); + assertTrue(vibrationThread.getVibrators().get(1).isVibrating()); + assertTrue(vibrationThread.getVibrators().get(2).isVibrating()); + + // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should + // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. + Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); + cancellingThread.start(); + + waitForCompletion(vibrationThread, 20); + waitForCompletion(cancellingThread); + + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); + assertFalse(vibrationThread.getVibrators().get(1).isVibrating()); + assertFalse(vibrationThread.getVibrators().get(2).isVibrating()); + } + + @Test + public void vibrate_multipleWaveformCancel_cancelsVibrationImmediately() throws Exception { + mockVibrators(1, 2); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); long vibrationId = 1; CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.createWaveform( - new long[]{5, 10}, new int[]{1, 2}, 0)) - .addVibrator(2, VibrationEffect.createWaveform( - new long[]{20, 30}, new int[]{3, 4}, 0)) - .addVibrator(3, VibrationEffect.createWaveform( - new long[]{10, 40}, new int[]{5, 6}, 0)) + new long[]{100, 100}, new int[]{1, 2}, 0)) + .addVibrator(2, VibrationEffect.createOneShot(100, 100)) .combine(); - VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect); - Thread.sleep(15); - assertTrue(thread.isAlive()); - assertTrue(thread.getVibrators().get(1).isVibrating()); - assertTrue(thread.getVibrators().get(2).isVibrating()); - assertTrue(thread.getVibrators().get(3).isVibrating()); + Thread.sleep(10); + assertTrue(vibrationThread.isAlive()); + assertTrue(vibrationThread.getVibrators().get(1).isVibrating()); + assertTrue(vibrationThread.getVibrators().get(2).isVibrating()); - thread.cancel(); - waitForCompletion(thread); - assertFalse(thread.getVibrators().get(1).isVibrating()); - assertFalse(thread.getVibrators().get(2).isVibrating()); - assertFalse(thread.getVibrators().get(3).isVibrating()); + // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should + // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. + Thread cancellingThread = new Thread(() -> vibrationThread.cancel()); + cancellingThread.start(); + + waitForCompletion(vibrationThread, 20); + waitForCompletion(cancellingThread); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); + assertFalse(vibrationThread.getVibrators().get(1).isVibrating()); + assertFalse(vibrationThread.getVibrators().get(2).isVibrating()); } @Test @@ -621,11 +709,11 @@ public class VibrationThreadTest { return thread; } - private void waitForCompletion(VibrationThread thread) { + private void waitForCompletion(Thread thread) { waitForCompletion(thread, TEST_TIMEOUT_MILLIS); } - private void waitForCompletion(VibrationThread thread, long timeout) { + private void waitForCompletion(Thread thread, long timeout) { try { thread.join(timeout); } catch (InterruptedException e) { 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 35876e4e1fde..e8888f496a01 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,9 +112,9 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentUris; import android.content.Context; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; -import android.content.IIntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -1509,20 +1509,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag() - throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); - sbn.getNotification().flags = - Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE; - mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", - sbn.getId(), sbn.getNotification(), sbn.getUserId()); - mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId()); - waitForIdle(); - verify(mListeners, times(1)).notifyPostedLocked(any(), any()); - verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), any()); - } - - @Test public void testUserInitiatedClearAll_noLeak() throws Exception { final NotificationRecord n = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); 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 ef96a2d5960a..6ca9de3ccc9e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1251,8 +1251,7 @@ public class ActivityRecordTests extends WindowTestsBase { topActivity.setState(RESUMED, "true"); doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( any() /* starting */, anyInt() /* configChanges */, - anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */, - anyBoolean() /* userLeaving */); + anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); topActivity.setShowWhenLocked(true); // Verify the stack-top activity is occluded keyguard. @@ -1299,7 +1298,7 @@ public class ActivityRecordTests extends WindowTestsBase { secondActivity.completeFinishing("test"); verify(secondActivity.mDisplayContent).ensureActivitiesVisible(null /* starting */, 0 /* configChanges */ , false /* preserveWindows */, - true /* notifyClients */, false /* userLeaving */); + true /* notifyClients */); // Finish the first activity firstActivity.finishing = true; @@ -1307,7 +1306,7 @@ public class ActivityRecordTests extends WindowTestsBase { firstActivity.completeFinishing("test"); verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */, 0 /* configChanges */ , false /* preserveWindows */, - true /* notifyClients */, false /* userLeaving */); + true /* notifyClients */); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index fc96b69a568b..22ee886c46fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -1151,7 +1151,7 @@ public class ActivityStackTests extends WindowTestsBase { final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING; - task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity, + task.startPausingLocked(false /* uiSleeping */, topActivity, "test"); verify(task).completePauseLocked(anyBoolean(), eq(topActivity)); } 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 11be74d1a8c7..30c100801002 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -89,6 +89,7 @@ import static org.mockito.Mockito.doCallRealMethod; import android.annotation.SuppressLint; import android.app.ActivityTaskManager; import android.app.WindowConfiguration; +import android.app.servertransaction.FixedRotationAdjustmentsItem; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; @@ -1468,6 +1469,33 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testClearIntermediateFixedRotation() throws RemoteException { + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + mDisplayContent.setFixedRotationLaunchingApp(activity, + (mDisplayContent.getRotation() + 1) % 4); + // Create a window so FixedRotationAdjustmentsItem can be sent. + createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin"); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity2.setVisible(false); + clearInvocations(mAtm.getLifecycleManager()); + // The first activity has applied fixed rotation but the second activity becomes the top + // before the transition is done and it has the same rotation as display, so the dispatched + // rotation adjustment of first activity must be cleared. + mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2, + false /* checkOpening */); + + final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor = + ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class); + verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction( + eq(activity.app.getThread()), adjustmentsCaptor.capture()); + assertFalse(activity.hasFixedRotationTransform()); + final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain( + activity.token, null /* fixedRotationAdjustments */); + // The captor may match other items. The first one must be the item to clear adjustments. + assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0)); + } + + @Test public void testRemoteRotation() { DisplayContent dc = createNewDisplay(); @@ -1613,12 +1641,11 @@ public class DisplayContentTests extends WindowTestsBase { // The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice. assertFalse(called[0]); called[0] = true; - mDisplayContent.ensureActivitiesVisible(null, 0, false, false, false); + mDisplayContent.ensureActivitiesVisible(null, 0, false, false); return null; - }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean(), - anyBoolean()); + }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean()); - mDisplayContent.ensureActivitiesVisible(null, 0, false, false, false); + mDisplayContent.ensureActivitiesVisible(null, 0, false, false); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 63ee5d05fada..d017c19cac86 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -100,8 +100,7 @@ public class RecentsAnimationTest extends WindowTestsBase { doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( any() /* starting */, anyInt() /* configChanges */, - anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */, - anyBoolean() /* userLeaving */); + anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); RecentsAnimationCallbacks recentsAnimation = startRecentsActivity( mRecentsComponent, true /* getRecentsAnimation */); @@ -192,8 +191,7 @@ public class RecentsAnimationTest extends WindowTestsBase { doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( any() /* starting */, anyInt() /* configChanges */, - anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */, - anyBoolean() /* userLeaving */); + anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt()); ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); doNothing().when(lifecycleManager).scheduleTransaction(any()); @@ -349,8 +347,7 @@ public class RecentsAnimationTest extends WindowTestsBase { doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId(); doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( any() /* starting */, anyInt() /* configChanges */, - anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */, - anyBoolean() /* userLeaving */); + anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(), true); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index e1bc90a2551c..afaf1b1fea0a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -297,7 +297,7 @@ public class SystemServicesTestRule implements TestRule { doReturn(true).when(mWmService.mRoot).hasAwakeDisplay(); // Called when moving activity to pinned stack. doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(), - anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); + anyInt(), anyBoolean(), anyBoolean()); spyOn(mWmService.mDisplayWindowSettings); spyOn(mWmService.mDisplayWindowSettingsProvider); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 10d2da00a852..d83e9c21fae7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -352,7 +352,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { boolean reuseCandidate) { final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea(); final Task rootTask = taskDisplayArea.getOrCreateRootTask(windowingMode, activityType, - false /* onTop */, null /* intent */, candidateTask /* candidateTask */); + false /* onTop */, null /* intent */, candidateTask /* candidateTask */, + null /* activityOptions */); assertEquals(reuseCandidate, rootTask == candidateTask); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index 6046c9b2c2b1..d1391412be11 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -198,6 +198,10 @@ public class TaskStackChangedListenerTest { @Override public void onTaskRemoved(int taskId) throws RemoteException { + if (taskCreatedLaunchLatch.getCount() == 1) { + // The test activity hasn't started. Ignore the noise from previous test. + return; + } params[0] = taskId; taskRemovedLatch.countDown(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index ccf2394e7f81..e4b865fd2941 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -28,6 +28,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerManager.GoToSleepReason; import android.os.PowerManager.WakeReason; import android.os.RemoteException; import android.util.proto.ProtoOutputStream; @@ -177,19 +178,19 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void startedWakingUp(@WakeReason int reason) { + public void startedWakingUp(@WakeReason int wakeReason) { } @Override - public void finishedWakingUp(@WakeReason int reason) { + public void finishedWakingUp(@WakeReason int wakeReason) { } @Override - public void startedGoingToSleep(int why) { + public void startedGoingToSleep(@GoToSleepReason int sleepReason) { } @Override - public void finishedGoingToSleep(int why) { + public void finishedGoingToSleep(@GoToSleepReason int sleepReason) { } @Override diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 8041fa9e05b4..e40bd8a6f420 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5212,7 +5212,7 @@ public class CarrierConfigManager { sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, ""); sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false); sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false); - sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, false); + sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true); sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0); } diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java index 250d9e8b212e..3b4cf75e7919 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -121,7 +121,7 @@ public final class PreciseDisconnectCause { public static final int BEARER_CAPABILITY_NOT_AUTHORIZED = 57; /** The requested bearer capability is not available at this time. */ public static final int BEARER_NOT_AVAIL = 58; - /** The service option is not availble at this time. */ + /** The service option is not available at this time. */ public static final int SERVICE_OPTION_NOT_AVAILABLE = 63; /** The equipment sending this cause does not support the bearer capability requested. */ public static final int BEARER_SERVICE_NOT_IMPLEMENTED = 65; diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.aidl b/telephony/java/android/telephony/SignalStrengthUpdateRequest.aidl new file mode 100644 index 000000000000..a45de2e58dd1 --- /dev/null +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2020 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.telephony; + +parcelable SignalStrengthUpdateRequest; diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java index 2a1640234144..af67ed279fab 100644 --- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java @@ -17,6 +17,8 @@ package android.telephony; import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -56,6 +58,11 @@ public final class SignalStrengthUpdateRequest implements Parcelable { */ private final boolean mIsSystemThresholdReportingRequestedWhileIdle; + /** + * A IBinder object as a token for server side to check if the request client is still living. + */ + private final IBinder mLiveToken; + private SignalStrengthUpdateRequest( @NonNull List<SignalThresholdInfo> signalThresholdInfos, boolean isReportingRequestedWhileIdle, @@ -66,6 +73,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { mIsReportingRequestedWhileIdle = isReportingRequestedWhileIdle; mIsSystemThresholdReportingRequestedWhileIdle = isSystemThresholdReportingRequestedWhileIdle; + mLiveToken = new Binder(); } /** @@ -148,6 +156,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { mSignalThresholdInfos = in.createTypedArrayList(SignalThresholdInfo.CREATOR); mIsReportingRequestedWhileIdle = in.readBoolean(); mIsSystemThresholdReportingRequestedWhileIdle = in.readBoolean(); + mLiveToken = in.readStrongBinder(); } /** @@ -178,6 +187,15 @@ public final class SignalStrengthUpdateRequest implements Parcelable { return mIsSystemThresholdReportingRequestedWhileIdle; } + /* + * @return the live token of the request + * + * @hide + */ + public @NonNull IBinder getLiveToken() { + return mLiveToken; + } + @Override public int describeContents() { return 0; @@ -188,6 +206,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { dest.writeTypedList(mSignalThresholdInfos); dest.writeBoolean(mIsReportingRequestedWhileIdle); dest.writeBoolean(mIsSystemThresholdReportingRequestedWhileIdle); + dest.writeStrongBinder(mLiveToken); } @Override @@ -199,10 +218,10 @@ public final class SignalStrengthUpdateRequest implements Parcelable { } SignalStrengthUpdateRequest request = (SignalStrengthUpdateRequest) other; - return request.mSignalThresholdInfos.equals(mSignalThresholdInfos) - && request.mIsReportingRequestedWhileIdle == mIsReportingRequestedWhileIdle - && request.mIsSystemThresholdReportingRequestedWhileIdle - == mIsSystemThresholdReportingRequestedWhileIdle; + return mSignalThresholdInfos.equals(request.mSignalThresholdInfos) + && mIsReportingRequestedWhileIdle == request.mIsReportingRequestedWhileIdle + && mIsSystemThresholdReportingRequestedWhileIdle + == request.mIsSystemThresholdReportingRequestedWhileIdle; } @Override @@ -233,6 +252,8 @@ public final class SignalStrengthUpdateRequest implements Parcelable { .append(mIsReportingRequestedWhileIdle) .append(" mIsSystemThresholdReportingRequestedWhileIdle=") .append(mIsSystemThresholdReportingRequestedWhileIdle) + .append(" mLiveToken") + .append(mLiveToken) .append("}").toString(); } diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index e06dcdb7e736..6c013df66840 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -384,7 +384,6 @@ public final class SmsManager { * where this operation may fail. * </p> * - * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC @@ -397,7 +396,6 @@ public final class SmsManager { * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * <code>RESULT_ERROR_NO_SERVICE</code><br> - * <code>RESULT_ERROR_NO_SERVICE</code><br> * <code>RESULT_ERROR_LIMIT_EXCEEDED</code><br> * <code>RESULT_ERROR_FDN_CHECK_FAILURE</code><br> * <code>RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> @@ -514,7 +512,6 @@ public final class SmsManager { * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * <code>RESULT_ERROR_NO_SERVICE</code><br> - * <code>RESULT_ERROR_NO_SERVICE</code><br> * <code>RESULT_ERROR_LIMIT_EXCEEDED</code><br> * <code>RESULT_ERROR_FDN_CHECK_FAILURE</code><br> * <code>RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> @@ -903,22 +900,20 @@ public final class SmsManager { * where this operation may fail. * </p> * - * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use - * the current default SMSC + * the current default SMSC * @param parts an <code>ArrayList</code> of strings that, in order, - * comprise the original message + * comprise the original message * @param sentIntents if not null, an <code>ArrayList</code> of - * <code>PendingIntent</code>s (one for each message part) that is - * broadcast when the corresponding message part has been sent. - * The result code will be <code>Activity.RESULT_OK</code> for success, - * or one of these errors:<br> + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * <code>RESULT_ERROR_NO_SERVICE</code><br> - * <code>RESULT_ERROR_NO_SERVICE</code><br> * <code>RESULT_ERROR_LIMIT_EXCEEDED</code><br> * <code>RESULT_ERROR_FDN_CHECK_FAILURE</code><br> * <code>RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> @@ -974,14 +969,14 @@ public final class SmsManager { * For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, * the sentIntent may include the extra "errorCode" containing a radio technology specific * value, generally only useful for troubleshooting.<br> - * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applications, - * which cause smaller number of SMS to be sent in checking period. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntents if not null, an <code>ArrayList</code> of - * <code>PendingIntent</code>s (one for each message part) that is - * broadcast when the corresponding message part has been delivered - * to the recipient. The raw pdu of the status report is in the - * extended data ("pdu"). + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). * * @throws IllegalArgumentException if destinationAddress or data are empty */ @@ -1163,22 +1158,21 @@ public final class SmsManager { * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions * where this operation may fail. * </p> - + * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use - * the current default SMSC + * the current default SMSC * @param parts an <code>ArrayList</code> of strings that, in order, - * comprise the original message + * comprise the original message * @param sentIntents if not null, an <code>ArrayList</code> of - * <code>PendingIntent</code>s (one for each message part) that is - * broadcast when the corresponding message part has been sent. - * The result code will be <code>Activity.RESULT_OK</code> for success, - * or one of these errors:<br> + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * <code>RESULT_ERROR_NO_SERVICE</code><br> - * <code>RESULT_ERROR_NO_SERVICE</code><br> * <code>RESULT_ERROR_LIMIT_EXCEEDED</code><br> * <code>RESULT_ERROR_FDN_CHECK_FAILURE</code><br> * <code>RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> @@ -1234,14 +1228,14 @@ public final class SmsManager { * For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, * the sentIntent may include the extra "errorCode" containing a radio technology specific * value, generally only useful for troubleshooting.<br> - * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applications, - * which cause smaller number of SMS to be sent in checking period. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntents if not null, an <code>ArrayList</code> of - * <code>PendingIntent</code>s (one for each message part) that is - * broadcast when the corresponding message part has been delivered - * to the recipient. The raw pdu of the status report is in the - * extended data ("pdu"). + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). * @param priority Priority level of the message * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 * --------------------------------- @@ -1380,7 +1374,6 @@ public final class SmsManager { * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * <code>RESULT_ERROR_NO_SERVICE</code><br> - * <code>RESULT_ERROR_NO_SERVICE</code><br> * <code>RESULT_ERROR_LIMIT_EXCEEDED</code><br> * <code>RESULT_ERROR_FDN_CHECK_FAILURE</code><br> * <code>RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index d4c2bc9d9b8b..af829915a3db 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2609,14 +2609,45 @@ public class SubscriptionManager { * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. + * outlined above. */ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @DurationMillisLong long timeoutMillis) { + setSubscriptionOverrideUnmetered(subId, overrideUnmetered, + TelephonyManager.getAllNetworkTypes(), timeoutMillis); + } + /** + * Temporarily override the billing relationship plan between a carrier and + * a specific subscriber to be considered unmetered. This will be reflected + * to apps via {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges()}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this override applies to. + * @param overrideUnmetered set if the billing relationship should be + * considered unmetered. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * requested state until explicitly cleared, or the next reboot, + * whichever happens first. + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. + */ + public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, + @NonNull @Annotation.NetworkType int[] networkTypes, + @DurationMillisLong long timeoutMillis) { final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED, - overrideValue, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); } /** @@ -2641,13 +2672,46 @@ public class SubscriptionManager { * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. + * outlined above. + */ + public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, + @DurationMillisLong long timeoutMillis) { + setSubscriptionOverrideCongested(subId, overrideCongested, + TelephonyManager.getAllNetworkTypes(), timeoutMillis); + } + + /** + * Temporarily override the billing relationship plan between a carrier and + * a specific subscriber to be considered congested. This will cause the + * device to delay certain network requests when possible, such as developer + * jobs that are willing to run in a flexible time window. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges()}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this override applies to. + * @param overrideCongested set if the subscription should be considered + * congested. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * requested state until explicitly cleared, or the next reboot, + * whichever happens first. + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. */ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, + @NonNull @Annotation.NetworkType int[] networkTypes, @DurationMillisLong long timeoutMillis) { final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED, - overrideValue, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2f577a99028e..216413be58a0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7805,18 +7805,23 @@ public class TelephonyManager { } /** - * Set IMS registration state + * Set IMS registration state on all active subscriptions. + * <p/> + * Use {@link android.telephony.ims.stub.ImsRegistrationImplBase#onRegistered} and + * {@link android.telephony.ims.stub.ImsRegistrationImplBase#onDeregistered} to set Ims + * registration state instead. + * + * @param registered whether ims is registered * - * @param Registration state * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setImsRegistrationState(boolean registered) { + public void setImsRegistrationState(final boolean registered) { try { - ITelephony telephony = getITelephony(); + final ITelephony telephony = getITelephony(); if (telephony != null) telephony.setImsRegistrationState(registered); - } catch (RemoteException e) { + } catch (final RemoteException e) { } } @@ -9806,9 +9811,16 @@ public class TelephonyManager { * @return true if mobile data is enabled. */ @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE, - android.Manifest.permission.MODIFY_PHONE_STATE}) + android.Manifest.permission.MODIFY_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled() { - return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); + try { + return isDataEnabledForReason(DATA_ENABLED_REASON_USER); + } catch (IllegalStateException ise) { + // TODO(b/176163590): Remove this catch once TelephonyManager is booting safely. + Log.e(TAG, "Error calling #isDataEnabled, returning default (false).", ise); + return false; + } } /** @@ -10053,7 +10065,7 @@ public class TelephonyManager { @SystemApi public boolean getDataEnabled(int subId) { try { - return isDataEnabledForReason(DATA_ENABLED_REASON_USER); + return isDataEnabledForReason(subId, DATA_ENABLED_REASON_USER); } catch (RuntimeException e) { Log.e(TAG, "Error calling isDataEnabledForReason e:" + e); } @@ -15200,4 +15212,85 @@ public class TelephonyManager { return networkType >= TelephonyManager.NETWORK_TYPE_UNKNOWN && networkType <= TelephonyManager.NETWORK_TYPE_NR; } + + /** + * Set a {@link SignalStrengthUpdateRequest} to receive notification when signal quality + * measurements breach the specified thresholds. + * + * To be notified, set the signal strength update request and then register + * {@link TelephonyManager#listen(PhoneStateListener, int)} with + * {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS}. The notification will arrive through + * {@link PhoneStateListener#onSignalStrengthsChanged(SignalStrength)}. + * + * To stop receiving the notification over the specified thresholds, pass the same + * {@link SignalStrengthUpdateRequest} object to + * {@link #clearSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)}. + * + * System will clean up the {@link SignalStrengthUpdateRequest} if the caller process died + * without calling {@link #clearSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)}. + * + * If this TelephonyManager object has been created with {@link #createForSubscriptionId}, + * applies to the given subId. Otherwise, applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. To request for multiple subIds, + * pass a request object to each TelephonyManager object created with + * {@link #createForSubscriptionId}. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * + * Note that the thresholds in the request will be used on a best-effort basis; the system may + * modify requests to multiplex various request sources or to optimize power consumption. The + * caller should not expect to be notified with the exactly the same thresholds. + * + * @see #clearSignalStrengthUpdateRequest(SignalStrengthUpdateRequest) + * + * @param request the SignalStrengthUpdateRequest to be set into the System + * + * @throws IllegalStateException if a new request is set with same subId from the same caller + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setSignalStrengthUpdateRequest(@NonNull SignalStrengthUpdateRequest request) { + Objects.requireNonNull(request, "request must not be null"); + + try { + ITelephony service = getITelephony(); + if (service != null) { + service.setSignalStrengthUpdateRequest(getSubId(), request, getOpPackageName()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setSignalStrengthUpdateRequest", e); + } + } + + /** + * Clear a {@link SignalStrengthUpdateRequest} from the system. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * + * <p>If the given request was not set before, this operation is a no-op. + * + * @see #setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest) + * + * @param request the SignalStrengthUpdateRequest to be cleared from the System + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void clearSignalStrengthUpdateRequest(@NonNull SignalStrengthUpdateRequest request) { + Objects.requireNonNull(request, "request must not be null"); + + try { + ITelephony service = getITelephony(); + if (service != null) { + service.clearSignalStrengthUpdateRequest(getSubId(), request, getOpPackageName()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#clearSignalStrengthUpdateRequest", e); + } + } } diff --git a/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.aidl b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.aidl new file mode 100644 index 000000000000..da31f9864cf1 --- /dev/null +++ b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package android.telephony.data; + + parcelable EpsBearerQosSessionAttributes;
\ No newline at end of file diff --git a/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java new file mode 100644 index 000000000000..041edc00c4d2 --- /dev/null +++ b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.data; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.QosSessionAttributes; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Provides Qos attributes of an EPS bearer. + * + * {@hide} + */ +@SystemApi +public final class EpsBearerQosSessionAttributes implements Parcelable, QosSessionAttributes { + private static final String TAG = EpsBearerQosSessionAttributes.class.getSimpleName(); + private final int mQci; + private final long mMaxUplinkBitRate; + private final long mMaxDownlinkBitRate; + private final long mGuaranteedUplinkBitRate; + private final long mGuaranteedDownlinkBitRate; + @NonNull private final List<InetSocketAddress> mRemoteAddresses; + + /** + * Quality of Service Class Identifier (QCI), see 3GPP TS 23.203 and 29.212. + * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85) + * defined in the spec and operator specific values in the range 128-254. + * + * @return the qci of the session + */ + public int getQci() { + return mQci; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedUplinkBitRate() { + return mGuaranteedUplinkBitRate; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedDownlinkBitRate() { + return mGuaranteedDownlinkBitRate; + } + + /** + * The maximum kbps that the network will accept. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max uplink bit rate in kbps + */ + public long getMaxUplinkBitRate() { + return mMaxUplinkBitRate; + } + + /** + * The maximum kbps that the network can provide. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max downlink bit rate in kbps + */ + public long getMaxDownlinkBitRate() { + return mMaxDownlinkBitRate; + } + + /** + * List of remote addresses associated with the Qos Session. The given uplink bit rates apply + * to this given list of remote addresses. + * + * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to + * all remote addresses that are not contained in a different set of attributes. + * + * @return list of remote socket addresses that the attributes apply to + */ + @NonNull + public List<InetSocketAddress> getRemoteAddresses() { + return mRemoteAddresses; + } + + /** + * ..ctor for attributes + * + * @param qci quality class indicator + * @param maxDownlinkBitRate the max downlink bit rate in kbps + * @param maxUplinkBitRate the max uplink bit rate in kbps + * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps + * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps + * @param remoteAddresses the remote addresses that the uplink bit rates apply to + * + * @hide + */ + public EpsBearerQosSessionAttributes(final int qci, + final long maxDownlinkBitRate, final long maxUplinkBitRate, + final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate, + @NonNull final List<InetSocketAddress> remoteAddresses) { + Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null"); + mQci = qci; + mMaxDownlinkBitRate = maxDownlinkBitRate; + mMaxUplinkBitRate = maxUplinkBitRate; + mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate; + mGuaranteedUplinkBitRate = guaranteedUplinkBitRate; + + final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses); + mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp); + } + + private static List<InetSocketAddress> copySocketAddresses( + @NonNull final List<InetSocketAddress> remoteAddresses) { + final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>(); + for (final InetSocketAddress socketAddress : remoteAddresses) { + if (socketAddress != null && socketAddress.getAddress() != null) { + remoteAddressesTemp.add(socketAddress); + } + } + return remoteAddressesTemp; + } + + private EpsBearerQosSessionAttributes(@NonNull final Parcel in) { + mQci = in.readInt(); + mMaxDownlinkBitRate = in.readLong(); + mMaxUplinkBitRate = in.readLong(); + mGuaranteedDownlinkBitRate = in.readLong(); + mGuaranteedUplinkBitRate = in.readLong(); + + final int size = in.readInt(); + final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + final byte[] addressBytes = in.createByteArray(); + final int port = in.readInt(); + try { + remoteAddresses.add( + new InetSocketAddress(InetAddress.getByAddress(addressBytes), port)); + } catch (final UnknownHostException e) { + // Impossible case since we filter out null values in the ..ctor + Log.e(TAG, "unable to unparcel remote address at index: " + i, e); + } + } + mRemoteAddresses = Collections.unmodifiableList(remoteAddresses); + } + + /** + * Creates attributes based off of a parcel + * @param in the parcel + * @return the attributes + */ + @NonNull + public static EpsBearerQosSessionAttributes create(@NonNull final Parcel in) { + return new EpsBearerQosSessionAttributes(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mQci); + dest.writeLong(mMaxDownlinkBitRate); + dest.writeLong(mMaxUplinkBitRate); + dest.writeLong(mGuaranteedDownlinkBitRate); + dest.writeLong(mGuaranteedUplinkBitRate); + + final int size = mRemoteAddresses.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final InetSocketAddress address = mRemoteAddresses.get(i); + dest.writeByteArray(address.getAddress().getAddress()); + dest.writeInt(address.getPort()); + } + } + + @NonNull + public static final Creator<EpsBearerQosSessionAttributes> CREATOR = + new Creator<EpsBearerQosSessionAttributes>() { + @NonNull + @Override + public EpsBearerQosSessionAttributes createFromParcel(@NonNull final Parcel in) { + return new EpsBearerQosSessionAttributes(in); + } + + @NonNull + @Override + public EpsBearerQosSessionAttributes[] newArray(final int size) { + return new EpsBearerQosSessionAttributes[size]; + } + }; +} diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index c14024975cf5..dda021e6172f 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -891,6 +891,12 @@ public final class ImsReasonInfo implements Parcelable { public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623; /** + * Call failed because of network congestion, resource is not available, + * or no circuit or channel available, etc. + */ + public static final int CODE_NETWORK_CONGESTION = 1624; + + /** * The dialed RTT call should be retried without RTT * @hide */ @@ -1076,6 +1082,7 @@ public final class ImsReasonInfo implements Parcelable { CODE_REJECT_VT_AVPF_NOT_ALLOWED, CODE_REJECT_ONGOING_ENCRYPTED_CALL, CODE_REJECT_ONGOING_CS_CALL, + CODE_NETWORK_CONGESTION, CODE_RETRY_ON_IMS_WITHOUT_RTT, CODE_OEM_CAUSE_1, CODE_OEM_CAUSE_2, @@ -1268,6 +1275,7 @@ public final class ImsReasonInfo implements Parcelable { sImsCodeMap.put(CODE_REJECT_VT_AVPF_NOT_ALLOWED, "CODE_REJECT_VT_AVPF_NOT_ALLOWED"); sImsCodeMap.put(CODE_REJECT_ONGOING_ENCRYPTED_CALL, "CODE_REJECT_ONGOING_ENCRYPTED_CALL"); sImsCodeMap.put(CODE_REJECT_ONGOING_CS_CALL, "CODE_REJECT_ONGOING_CS_CALL"); + sImsCodeMap.put(CODE_NETWORK_CONGESTION, "CODE_NETWORK_CONGESTION"); sImsCodeMap.put(CODE_RETRY_ON_IMS_WITHOUT_RTT, "CODE_RETRY_ON_IMS_WITHOUT_RTT"); sImsCodeMap.put(CODE_OEM_CAUSE_1, "CODE_OEM_CAUSE_1"); sImsCodeMap.put(CODE_OEM_CAUSE_2, "CODE_OEM_CAUSE_2"); diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java index 8ed48388d352..b430befbc024 100644 --- a/telephony/java/android/telephony/ims/RegistrationManager.java +++ b/telephony/java/android/telephony/ims/RegistrationManager.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -114,6 +115,22 @@ public interface RegistrationManager { AccessNetworkConstants.TRANSPORT_TYPE_WLAN); }}; + /** @hide */ + @NonNull + static String registrationStateToString( + final @NetworkRegistrationInfo.RegistrationState int value) { + switch (value) { + case REGISTRATION_STATE_NOT_REGISTERED: + return "REGISTRATION_STATE_NOT_REGISTERED"; + case REGISTRATION_STATE_REGISTERING: + return "REGISTRATION_STATE_REGISTERING"; + case REGISTRATION_STATE_REGISTERED: + return "REGISTRATION_STATE_REGISTERED"; + default: + return Integer.toString(value); + } + } + /** * Callback class for receiving IMS network Registration callback events. * @see #registerImsRegistrationCallback(Executor, RegistrationCallback) diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 46c9dde6023e..15f6c4119c46 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -49,6 +49,7 @@ import android.telephony.RadioAccessFamily; import android.telephony.RadioAccessSpecifier; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import android.telephony.SignalStrengthUpdateRequest; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; @@ -2388,4 +2389,17 @@ interface ITelephony { * their mobile plan. */ String getMobileProvisioningUrl(); + + /** + * Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the + * specified thresholds. + */ + void setSignalStrengthUpdateRequest(int subId, in SignalStrengthUpdateRequest request, + String callingPackage); + + /** + * Clear a SignalStrengthUpdateRequest from system. + */ + void clearSignalStrengthUpdateRequest(int subId, in SignalStrengthUpdateRequest request, + String callingPackage); } diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index 1110790c373f..d1a68d4e9cb2 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -117,6 +117,7 @@ package android.test.mock { method public void sendOrderedBroadcast(android.content.Intent, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendStickyBroadcast(android.content.Intent); + method public void sendStickyBroadcast(android.content.Intent, android.os.Bundle); method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 98e7b53a30cc..5391bd8642d5 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -493,6 +493,11 @@ public class MockContext extends Context { } @Override + public void sendStickyBroadcast(Intent intent, Bundle options) { + throw new UnsupportedOperationException(); + } + + @Override public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index 412a3c383785..907c9b5f45ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -51,6 +52,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 178015460) class CloseImeAutoOpenWindowToAppTest( testName: String, flickerSpec: Flicker 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 663d45607f93..4fd88f10bac9 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 @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -51,6 +52,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 178015460) class CloseImeWindowToAppTest( testName: String, flickerSpec: Flicker 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 bfe5264ed358..765bf6d39e21 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 @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -52,6 +53,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 178015460) class CloseImeWindowToHomeTest( testName: String, flickerSpec: Flicker 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 28a8bd385779..b937f0817498 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 @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -53,6 +54,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 178015460) class OpenImeWindowTest( testName: String, flickerSpec: Flicker diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 3d8deb5cfc8d..401d87af5a33 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -88,7 +88,7 @@ public class StagedRollbackTest { * Enable rollback phase. */ @Test - public void testBadApkOnly_Phase1() throws Exception { + public void testBadApkOnly_Phase1_Install() throws Exception { Uninstall.packages(TestApp.A); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); @@ -104,7 +104,7 @@ public class StagedRollbackTest { * Confirm that rollback was successfully enabled. */ @Test - public void testBadApkOnly_Phase2() throws Exception { + public void testBadApkOnly_Phase2_VerifyInstall() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); InstallUtils.processUserData(TestApp.A); @@ -129,7 +129,7 @@ public class StagedRollbackTest { * Trigger rollback phase. */ @Test - public void testBadApkOnly_Phase3() throws Exception { + public void testBadApkOnly_Phase3_Crash() throws Exception { // One more crash to trigger rollback RollbackUtils.sendCrashBroadcast(TestApp.A, 1); } @@ -139,7 +139,7 @@ public class StagedRollbackTest { * Confirm rollback phase. */ @Test - public void testBadApkOnly_Phase4() throws Exception { + public void testBadApkOnly_Phase4_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); InstallUtils.processUserData(TestApp.A); @@ -158,7 +158,7 @@ public class StagedRollbackTest { * Stage install an apk with rollback that will be later triggered by unattributable crash. */ @Test - public void testNativeWatchdogTriggersRollback_Phase1() throws Exception { + public void testNativeWatchdogTriggersRollback_Phase1_Install() throws Exception { Uninstall.packages(TestApp.A); Install.single(TestApp.A1).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); @@ -170,7 +170,7 @@ public class StagedRollbackTest { * Verify the rollback is available. */ @Test - public void testNativeWatchdogTriggersRollback_Phase2() throws Exception { + public void testNativeWatchdogTriggersRollback_Phase2_VerifyInstall() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), @@ -181,7 +181,7 @@ public class StagedRollbackTest { * Verify the rollback is committed after crashing. */ @Test - public void testNativeWatchdogTriggersRollback_Phase3() throws Exception { + public void testNativeWatchdogTriggersRollback_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), @@ -192,7 +192,7 @@ public class StagedRollbackTest { * Stage install an apk with rollback that will be later triggered by unattributable crash. */ @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception { + public void testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA() throws Exception { Uninstall.packages(TestApp.A); Install.single(TestApp.A1).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); @@ -204,7 +204,7 @@ public class StagedRollbackTest { * Verify the rollback is available and then install another package with rollback. */ @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception { + public void testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), @@ -222,7 +222,7 @@ public class StagedRollbackTest { * Verify the rollbacks are available. */ @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception { + public void testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); RollbackManager rm = RollbackUtils.getRollbackManager(); @@ -236,7 +236,7 @@ public class StagedRollbackTest { * Verify the rollbacks are committed after crashing. */ @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception { + public void testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); RollbackManager rm = RollbackUtils.getRollbackManager(); @@ -247,7 +247,7 @@ public class StagedRollbackTest { } @Test - public void testPreviouslyAbandonedRollbacks_Phase1() throws Exception { + public void testPreviouslyAbandonedRollbacks_Phase1_InstallAndAbandon() throws Exception { Uninstall.packages(TestApp.A); Install.single(TestApp.A1).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); @@ -261,7 +261,7 @@ public class StagedRollbackTest { } @Test - public void testPreviouslyAbandonedRollbacks_Phase2() throws Exception { + public void testPreviouslyAbandonedRollbacks_Phase2_Rollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); InstallUtils.processUserData(TestApp.A); @@ -272,7 +272,7 @@ public class StagedRollbackTest { } @Test - public void testPreviouslyAbandonedRollbacks_Phase3() throws Exception { + public void testPreviouslyAbandonedRollbacks_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); InstallUtils.processUserData(TestApp.A); Uninstall.packages(TestApp.A); @@ -284,7 +284,7 @@ public class StagedRollbackTest { } @Test - public void testRollbackWhitelistedApp_Phase1() throws Exception { + public void testRollbackAllowlistedApp_Phase1_Install() throws Exception { // Remove available rollbacks String pkgName = getModuleMetadataPackageName(); RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName); @@ -296,7 +296,7 @@ public class StagedRollbackTest { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.MANAGE_ROLLBACKS); - // Re-install a whitelisted app with rollbacks enabled + // Re-install a allowlisted app with rollbacks enabled String filePath = InstrumentationRegistry.getInstrumentation().getContext() .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir; TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath)); @@ -305,12 +305,12 @@ public class StagedRollbackTest { } @Test - public void testRollbackWhitelistedApp_Phase2() throws Exception { + public void testRollbackAllowlistedApp_Phase2_VerifyInstall() throws Exception { assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull(); } @Test - public void testRollbackDataPolicy_Phase1() throws Exception { + public void testRollbackDataPolicy_Phase1_Install() throws Exception { Uninstall.packages(TestApp.A, TestApp.B, TestApp.C); Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit(); // Write user data version = 1 @@ -328,7 +328,7 @@ public class StagedRollbackTest { } @Test - public void testRollbackDataPolicy_Phase2() throws Exception { + public void testRollbackDataPolicy_Phase2_Rollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); // Write user data version = 2 @@ -341,7 +341,7 @@ public class StagedRollbackTest { } @Test - public void testRollbackDataPolicy_Phase3() throws Exception { + public void testRollbackDataPolicy_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); assertThat(InstallUtils.getInstalledVersion(TestApp.C)).isEqualTo(1); @@ -355,7 +355,7 @@ public class StagedRollbackTest { } @Test - public void testCleanUp() throws Exception { + public void expireRollbacks() throws Exception { // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks // so there is only one rollback to commit when testing native crashes. @@ -378,7 +378,7 @@ public class StagedRollbackTest { APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex"); @Test - public void testRollbackApexWithApk_Phase1() throws Exception { + public void testRollbackApexWithApk_Phase1_Install() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); InstallUtils.processUserData(TestApp.A); @@ -388,7 +388,7 @@ public class StagedRollbackTest { } @Test - public void testRollbackApexWithApk_Phase2() throws Exception { + public void testRollbackApexWithApk_Phase2_Rollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); InstallUtils.processUserData(TestApp.A); @@ -416,7 +416,7 @@ public class StagedRollbackTest { } @Test - public void testRollbackApexWithApk_Phase3() throws Exception { + public void testRollbackApexWithApk_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); InstallUtils.processUserData(TestApp.A); @@ -426,7 +426,7 @@ public class StagedRollbackTest { * Installs an apex with an apk that can crash. */ @Test - public void testRollbackApexWithApkCrashing_Phase1() throws Exception { + public void testRollbackApexWithApkCrashing_Phase1_Install() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged() .setEnableRollback().commit(); @@ -437,7 +437,7 @@ public class StagedRollbackTest { * Verifies rollback has been enabled successfully. Then makes TestApp.A crash. */ @Test - public void testRollbackApexWithApkCrashing_Phase2() throws Exception { + public void testRollbackApexWithApkCrashing_Phase2_Crash() throws Exception { assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); @@ -452,20 +452,20 @@ public class StagedRollbackTest { } @Test - public void testRollbackApexWithApkCrashing_Phase3() throws Exception { + public void testRollbackApexWithApkCrashing_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); } @Test - public void testRollbackApexDataDirectories_Phase1() throws Exception { + public void testRollbackApexDataDirectories_Phase1_Install() throws Exception { int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() .commit(); InstallUtils.waitForSessionReady(sessionId); } @Test - public void testRollbackApexDataDirectories_Phase2() throws Exception { + public void testRollbackApexDataDirectories_Phase2_Rollback() throws Exception { RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); @@ -477,19 +477,19 @@ public class StagedRollbackTest { } @Test - public void testRollbackApkDataDirectories_Phase1() throws Exception { + public void testRollbackApkDataDirectories_Phase1_InstallV1() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); Install.single(TestApp.A1).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); } @Test - public void testRollbackApkDataDirectories_Phase2() throws Exception { + public void testRollbackApkDataDirectories_Phase2_InstallV2() throws Exception { Install.single(TestApp.A2).setStaged().setEnableRollback().commit(); } @Test - public void testRollbackApkDataDirectories_Phase3() throws Exception { + public void testRollbackApkDataDirectories_Phase3_Rollback() throws Exception { RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A); RollbackUtils.rollback(available.getRollbackId(), TestApp.A2); RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 94950dc456ca..1d5730fb4427 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -97,14 +97,14 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { public void setUp() throws Exception { deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); - runPhase("testCleanUp"); + runPhase("expireRollbacks"); mLogger.start(getDevice()); } @After public void tearDown() throws Exception { mLogger.stop(); - runPhase("testCleanUp"); + runPhase("expireRollbacks"); deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*", @@ -139,22 +139,27 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } } + private void waitForDeviceNotAvailable(long timeout, TimeUnit unit) { + assertWithMessage("waitForDeviceNotAvailable() timed out in %s %s", timeout, unit) + .that(getDevice().waitForDeviceNotAvailable(unit.toMillis(timeout))).isTrue(); + } + /** * Tests watchdog triggered staged rollbacks involving only apks. */ @Test public void testBadApkOnly() throws Exception { - runPhase("testBadApkOnly_Phase1"); + runPhase("testBadApkOnly_Phase1_Install"); getDevice().reboot(); - runPhase("testBadApkOnly_Phase2"); + runPhase("testBadApkOnly_Phase2_VerifyInstall"); // Trigger rollback and wait for reboot to happen - runPhase("testBadApkOnly_Phase3"); - assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(2))).isTrue(); + runPhase("testBadApkOnly_Phase3_Crash"); + waitForDeviceNotAvailable(2, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); - runPhase("testBadApkOnly_Phase4"); + runPhase("testBadApkOnly_Phase4_VerifyRollback"); assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); @@ -163,12 +168,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { @Test public void testNativeWatchdogTriggersRollback() throws Exception { - runPhase("testNativeWatchdogTriggersRollback_Phase1"); + runPhase("testNativeWatchdogTriggersRollback_Phase1_Install"); // Reboot device to activate staged package getDevice().reboot(); - runPhase("testNativeWatchdogTriggersRollback_Phase2"); + runPhase("testNativeWatchdogTriggersRollback_Phase2_VerifyInstall"); // crash system_server enough times to trigger a rollback crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); @@ -181,11 +186,11 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // 3. Staged rollback session becomes ready. // 4. Device actually reboots. // So we give a generous timeout here. - assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue(); + waitForDeviceNotAvailable(5, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); // verify rollback committed - runPhase("testNativeWatchdogTriggersRollback_Phase3"); + runPhase("testNativeWatchdogTriggersRollback_Phase3_VerifyRollback"); assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); @@ -198,15 +203,15 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { assumeTrue(isCheckpointSupported()); // Install a package with rollback enabled. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1"); + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA"); getDevice().reboot(); // Once previous staged install is applied, install another package - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2"); + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB"); getDevice().reboot(); // Verify the new staged install has also been applied successfully. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3"); + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall"); // crash system_server enough times to trigger a rollback crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); @@ -219,11 +224,11 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // 3. Staged rollback session becomes ready. // 4. Device actually reboots. // So we give a generous timeout here. - assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue(); + waitForDeviceNotAvailable(5, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); // verify all available rollbacks have been committed - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4"); + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback"); assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); @@ -235,33 +240,33 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @Test public void testPreviouslyAbandonedRollbacks() throws Exception { - runPhase("testPreviouslyAbandonedRollbacks_Phase1"); + runPhase("testPreviouslyAbandonedRollbacks_Phase1_InstallAndAbandon"); getDevice().reboot(); - runPhase("testPreviouslyAbandonedRollbacks_Phase2"); + runPhase("testPreviouslyAbandonedRollbacks_Phase2_Rollback"); getDevice().reboot(); - runPhase("testPreviouslyAbandonedRollbacks_Phase3"); + runPhase("testPreviouslyAbandonedRollbacks_Phase3_VerifyRollback"); } /** - * Tests we can enable rollback for a whitelisted app. + * Tests we can enable rollback for a allowlisted app. */ @Test - public void testRollbackWhitelistedApp() throws Exception { + public void testRollbackAllowlistedApp() throws Exception { assumeTrue(hasMainlineModule()); - runPhase("testRollbackWhitelistedApp_Phase1"); + runPhase("testRollbackAllowlistedApp_Phase1_Install"); getDevice().reboot(); - runPhase("testRollbackWhitelistedApp_Phase2"); + runPhase("testRollbackAllowlistedApp_Phase2_VerifyInstall"); } @Test public void testRollbackDataPolicy() throws Exception { List<String> before = getSnapshotDirectories("/data/misc_ce/0/rollback"); - runPhase("testRollbackDataPolicy_Phase1"); + runPhase("testRollbackDataPolicy_Phase1_Install"); getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase2"); + runPhase("testRollbackDataPolicy_Phase2_Rollback"); getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase3"); + runPhase("testRollbackDataPolicy_Phase3_VerifyRollback"); // Verify snapshots are deleted after restoration List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback"); @@ -279,11 +284,11 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { public void testRollbackApexWithApk() throws Exception { getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); pushTestApex(); - runPhase("testRollbackApexWithApk_Phase1"); + runPhase("testRollbackApexWithApk_Phase1_Install"); getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase2"); + runPhase("testRollbackApexWithApk_Phase2_Rollback"); getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase3"); + runPhase("testRollbackApexWithApk_Phase3_VerifyRollback"); } /** @@ -295,15 +300,15 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { pushTestApex(); // Install an apex with apk that crashes - runPhase("testRollbackApexWithApkCrashing_Phase1"); + runPhase("testRollbackApexWithApkCrashing_Phase1_Install"); getDevice().reboot(); // Verify apex was installed and then crash the apk - runPhase("testRollbackApexWithApkCrashing_Phase2"); + runPhase("testRollbackApexWithApkCrashing_Phase2_Crash"); // Wait for crash to trigger rollback - assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue(); + waitForDeviceNotAvailable(5, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); // Verify rollback occurred due to crash of apk-in-apex - runPhase("testRollbackApexWithApkCrashing_Phase3"); + runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback"); assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); @@ -323,12 +328,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { String oldFilePath2 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_2; runAsRoot(() -> { - assertThat(getDevice().pushString(TEST_STRING_1, oldFilePath1)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_2, oldFilePath2)).isTrue(); + pushString(TEST_STRING_1, oldFilePath1); + pushString(TEST_STRING_2, oldFilePath2); }); // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1"); + runPhase("testRollbackApexDataDirectories_Phase1_Install"); getDevice().reboot(); // Replace files in data directory @@ -338,20 +343,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runAsRoot(() -> { getDevice().deleteFile(oldFilePath1); getDevice().deleteFile(oldFilePath2); - assertThat(getDevice().pushString(TEST_STRING_3, newFilePath3)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_4, newFilePath4)).isTrue(); + pushString(TEST_STRING_3, newFilePath3); + pushString(TEST_STRING_4, newFilePath4); }); // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2"); + runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); getDevice().reboot(); // Verify that old files have been restored and new files are gone runAsRoot(() -> { - assertThat(getDevice().pullFileContents(oldFilePath1)).isEqualTo(TEST_STRING_1); - assertThat(getDevice().pullFileContents(oldFilePath2)).isEqualTo(TEST_STRING_2); - assertThat(getDevice().pullFile(newFilePath3)).isNull(); - assertThat(getDevice().pullFile(newFilePath4)).isNull(); + assertFileContents(TEST_STRING_1, oldFilePath1); + assertFileContents(TEST_STRING_2, oldFilePath2); + assertFileNotExists(newFilePath3); + assertFileNotExists(newFilePath4); }); // Verify snapshots are deleted after restoration @@ -377,12 +382,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { String oldFilePath2 = apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; runAsRoot(() -> { - assertThat(getDevice().pushString(TEST_STRING_1, oldFilePath1)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_2, oldFilePath2)).isTrue(); + pushString(TEST_STRING_1, oldFilePath1); + pushString(TEST_STRING_2, oldFilePath2); }); // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1"); + runPhase("testRollbackApexDataDirectories_Phase1_Install"); getDevice().reboot(); // Replace files in data directory @@ -393,20 +398,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runAsRoot(() -> { getDevice().deleteFile(oldFilePath1); getDevice().deleteFile(oldFilePath2); - assertThat(getDevice().pushString(TEST_STRING_3, newFilePath3)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_4, newFilePath4)).isTrue(); + pushString(TEST_STRING_3, newFilePath3); + pushString(TEST_STRING_4, newFilePath4); }); // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2"); + runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); getDevice().reboot(); // Verify that old files have been restored and new files are gone runAsRoot(() -> { - assertThat(getDevice().pullFileContents(oldFilePath1)).isEqualTo(TEST_STRING_1); - assertThat(getDevice().pullFileContents(oldFilePath2)).isEqualTo(TEST_STRING_2); - assertThat(getDevice().pullFile(newFilePath3)).isNull(); - assertThat(getDevice().pullFile(newFilePath4)).isNull(); + assertFileContents(TEST_STRING_1, oldFilePath1); + assertFileContents(TEST_STRING_2, oldFilePath2); + assertFileNotExists(newFilePath3); + assertFileNotExists(newFilePath4); }); // Verify snapshots are deleted after restoration @@ -431,12 +436,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { String oldFilePath2 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; runAsRoot(() -> { - assertThat(getDevice().pushString(TEST_STRING_1, oldFilePath1)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_2, oldFilePath2)).isTrue(); + pushString(TEST_STRING_1, oldFilePath1); + pushString(TEST_STRING_2, oldFilePath2); }); // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1"); + runPhase("testRollbackApexDataDirectories_Phase1_Install"); getDevice().reboot(); // Replace files in data directory @@ -446,20 +451,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runAsRoot(() -> { getDevice().deleteFile(oldFilePath1); getDevice().deleteFile(oldFilePath2); - assertThat(getDevice().pushString(TEST_STRING_3, newFilePath3)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_4, newFilePath4)).isTrue(); + pushString(TEST_STRING_3, newFilePath3); + pushString(TEST_STRING_4, newFilePath4); }); // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2"); + runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); getDevice().reboot(); // Verify that old files have been restored and new files are gone runAsRoot(() -> { - assertThat(getDevice().pullFileContents(oldFilePath1)).isEqualTo(TEST_STRING_1); - assertThat(getDevice().pullFileContents(oldFilePath2)).isEqualTo(TEST_STRING_2); - assertThat(getDevice().pullFile(newFilePath3)).isNull(); - assertThat(getDevice().pullFile(newFilePath4)).isNull(); + assertFileContents(TEST_STRING_1, oldFilePath1); + assertFileContents(TEST_STRING_2, oldFilePath2); + assertFileNotExists(newFilePath3); + assertFileNotExists(newFilePath4); }); // Verify snapshots are deleted after restoration @@ -477,18 +482,18 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { @Test public void testRollbackApkDataDirectories_De() throws Exception { // Install version 1 of TESTAPP_A - runPhase("testRollbackApkDataDirectories_Phase1"); + runPhase("testRollbackApkDataDirectories_Phase1_InstallV1"); // Push files to apk data directory String oldFilePath1 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_1; String oldFilePath2 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_2; runAsRoot(() -> { - assertThat(getDevice().pushString(TEST_STRING_1, oldFilePath1)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_2, oldFilePath2)).isTrue(); + pushString(TEST_STRING_1, oldFilePath1); + pushString(TEST_STRING_2, oldFilePath2); }); // Install version 2 of TESTAPP_A with rollback enabled - runPhase("testRollbackApkDataDirectories_Phase2"); + runPhase("testRollbackApkDataDirectories_Phase2_InstallV2"); getDevice().reboot(); // Replace files in data directory @@ -497,20 +502,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runAsRoot(() -> { getDevice().deleteFile(oldFilePath1); getDevice().deleteFile(oldFilePath2); - assertThat(getDevice().pushString(TEST_STRING_3, newFilePath3)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_4, newFilePath4)).isTrue(); + pushString(TEST_STRING_3, newFilePath3); + pushString(TEST_STRING_4, newFilePath4); }); // Roll back the APK - runPhase("testRollbackApkDataDirectories_Phase3"); + runPhase("testRollbackApkDataDirectories_Phase3_Rollback"); getDevice().reboot(); // Verify that old files have been restored and new files are gone runAsRoot(() -> { - assertThat(getDevice().pullFileContents(oldFilePath1)).isEqualTo(TEST_STRING_1); - assertThat(getDevice().pullFileContents(oldFilePath2)).isEqualTo(TEST_STRING_2); - assertThat(getDevice().pullFile(newFilePath3)).isNull(); - assertThat(getDevice().pullFile(newFilePath4)).isNull(); + assertFileContents(TEST_STRING_1, oldFilePath1); + assertFileContents(TEST_STRING_2, oldFilePath2); + assertFileNotExists(newFilePath3); + assertFileNotExists(newFilePath4); }); } @@ -524,12 +529,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { String oldFilePath2 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; runAsRoot(() -> { - assertThat(getDevice().pushString(TEST_STRING_1, oldFilePath1)).isTrue(); - assertThat(getDevice().pushString(TEST_STRING_2, oldFilePath2)).isTrue(); + pushString(TEST_STRING_1, oldFilePath1); + pushString(TEST_STRING_2, oldFilePath2); }); // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1"); + runPhase("testRollbackApexDataDirectories_Phase1_Install"); getDevice().reboot(); List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); @@ -538,10 +543,10 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test assertThat(after).hasSize(1); // Expire all rollbacks and check CE snapshot directories are deleted - runPhase("testCleanUp"); + runPhase("expireRollbacks"); runAsRoot(() -> { for (String dir : after) { - assertThat(getDevice().getFileEntry(dir)).isNull(); + assertFileNotExists(dir); } }); } @@ -560,6 +565,23 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().reboot(); } + private void pushString(String contents, String path) throws Exception { + assertWithMessage("Failed to push file to device, content=%s path=%s", contents, path) + .that(getDevice().pushString(contents, path)).isTrue(); + } + + private void assertFileContents(String expectedContents, String path) throws Exception { + String actualContents = getDevice().pullFileContents(path); + assertWithMessage("Failed to retrieve file=%s", path).that(actualContents).isNotNull(); + assertWithMessage("Mismatched file contents, path=%s", path) + .that(actualContents).isEqualTo(expectedContents); + } + + private void assertFileNotExists(String path) throws Exception { + assertWithMessage("File shouldn't exist, path=%s", path) + .that(getDevice().getFileEntry(path)).isNull(); + } + private static String apexDataDirDeSys(String apexName) { return String.format("/data/misc/apexdata/%s", apexName); } diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 3d4dc4d67dcc..dc9e587332cb 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; @@ -40,6 +41,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkSpecifier; +import android.net.QosFilter; import android.net.SocketKeepalive; import android.net.UidRange; import android.os.ConditionVariable; @@ -47,10 +49,12 @@ import android.os.HandlerThread; import android.os.Message; import android.util.Log; +import com.android.net.module.util.ArrayTrackRecord; import com.android.server.connectivity.ConnectivityConstants; import com.android.testutils.HandlerUtils; import com.android.testutils.TestableNetworkCallback; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -71,6 +75,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { // start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem. private long mKeepaliveResponseDelay = 0L; private Integer mExpectedKeepaliveSlot = null; + private final ArrayTrackRecord<CallbackType>.ReadHead mCallbackHistory = + new ArrayTrackRecord<CallbackType>().newReadHead(); public NetworkAgentWrapper(int transport, LinkProperties linkProperties, NetworkCapabilities ncTemplate, Context context) throws Exception { @@ -157,6 +163,20 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { } @Override + public void onQosCallbackRegistered(final int qosCallbackId, + final @NonNull QosFilter filter) { + Log.i(mWrapper.mLogTag, "onQosCallbackRegistered"); + mWrapper.mCallbackHistory.add( + new CallbackType.OnQosCallbackRegister(qosCallbackId, filter)); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + Log.i(mWrapper.mLogTag, "onQosCallbackUnregistered"); + mWrapper.mCallbackHistory.add(new CallbackType.OnQosCallbackUnregister(qosCallbackId)); + } + + @Override protected void preventAutomaticReconnect() { mWrapper.mPreventReconnectReceived.open(); } @@ -279,7 +299,60 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { return mNetworkCapabilities; } + public @NonNull ArrayTrackRecord<CallbackType>.ReadHead getCallbackHistory() { + return mCallbackHistory; + } + public void waitForIdle(long timeoutMs) { HandlerUtils.waitForIdle(mHandlerThread, timeoutMs); } + + abstract static class CallbackType { + final int mQosCallbackId; + + protected CallbackType(final int qosCallbackId) { + mQosCallbackId = qosCallbackId; + } + + static class OnQosCallbackRegister extends CallbackType { + final QosFilter mFilter; + OnQosCallbackRegister(final int qosCallbackId, final QosFilter filter) { + super(qosCallbackId); + mFilter = filter; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackRegister that = (OnQosCallbackRegister) o; + return mQosCallbackId == that.mQosCallbackId + && Objects.equals(mFilter, that.mFilter); + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId, mFilter); + } + } + + static class OnQosCallbackUnregister extends CallbackType { + OnQosCallbackUnregister(final int qosCallbackId) { + super(qosCallbackId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackUnregister that = (OnQosCallbackUnregister) o; + return mQosCallbackId == that.mQosCallbackId; + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId); + } + } + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 46302698a86c..c5e6c3507ebf 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -167,6 +167,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IQosCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; @@ -190,6 +191,9 @@ import android.net.NetworkStackClient; import android.net.NetworkState; import android.net.NetworkTestResultParcelable; import android.net.ProxyInfo; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.RouteInfoParcel; @@ -218,6 +222,7 @@ import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -226,10 +231,12 @@ import android.security.Credentials; import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -251,6 +258,7 @@ import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; +import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -368,6 +376,8 @@ public class ConnectivityServiceTest { private WrappedMultinetworkPolicyTracker mPolicyTracker; private HandlerThread mAlarmManagerThread; private TestNetIdManager mNetIdManager; + private QosCallbackMockHelper mQosCallbackMockHelper; + private QosCallbackTracker mQosCallbackTracker; @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; @@ -1395,6 +1405,7 @@ public class ConnectivityServiceTest { mService.systemReadyInternal(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); + mQosCallbackTracker = mock(QosCallbackTracker.class); // Ensure that the default setting for Captive Portals is used for most tests setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); @@ -1470,6 +1481,11 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.disconnect(); mEthernetNetworkAgent = null; } + + if (mQosCallbackMockHelper != null) { + mQosCallbackMockHelper.tearDown(); + mQosCallbackMockHelper = null; + } mMockVpn.disconnect(); waitForIdle(); @@ -4379,7 +4395,7 @@ public class ConnectivityServiceTest { } private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception { - // Ensure the network is disconnected before we do anything. + // Ensure the network is disconnected before anything else occurs if (mWiFiNetworkAgent != null) { assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); } @@ -8512,7 +8528,7 @@ public class ConnectivityServiceTest { TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(), nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, null, - 0, INVALID_UID); + 0, INVALID_UID, mQosCallbackTracker); } @Test @@ -8890,7 +8906,7 @@ public class ConnectivityServiceTest { @Test public void testInvalidRequestTypes() { - final int[] invalidReqTypeInts = new int[] {-1, NetworkRequest.Type.NONE.ordinal(), + final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(), NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length}; final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); @@ -8903,4 +8919,151 @@ public class ConnectivityServiceTest { ); } } + + private class QosCallbackMockHelper { + @NonNull public final QosFilter mFilter; + @NonNull public final IQosCallback mCallback; + @NonNull public final TestNetworkAgentWrapper mAgentWrapper; + @NonNull private final List<IQosCallback> mCallbacks = new ArrayList(); + + QosCallbackMockHelper() throws Exception { + Log.d(TAG, "QosCallbackMockHelper: "); + mFilter = mock(QosFilter.class); + + // Ensure the network is disconnected before anything else occurs + assertNull(mCellNetworkAgent); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + verifyActiveNetwork(TRANSPORT_CELLULAR); + waitForIdle(); + final Network network = mCellNetworkAgent.getNetwork(); + + final Pair<IQosCallback, IBinder> pair = createQosCallback(); + mCallback = pair.first; + + when(mFilter.getNetwork()).thenReturn(network); + when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mAgentWrapper = mCellNetworkAgent; + } + + void registerQosCallback(@NonNull final QosFilter filter, + @NonNull final IQosCallback callback) { + mCallbacks.add(callback); + final NetworkAgentInfo nai = + mService.getNetworkAgentInfoForNetwork(filter.getNetwork()); + mService.registerQosCallbackInternal(filter, callback, nai); + } + + void tearDown() { + for (int i = 0; i < mCallbacks.size(); i++) { + mService.unregisterQosCallback(mCallbacks.get(i)); + } + } + } + + private Pair<IQosCallback, IBinder> createQosCallback() { + final IQosCallback callback = mock(IQosCallback.class); + final IBinder binder = mock(Binder.class); + when(callback.asBinder()).thenReturn(binder); + when(binder.isBinderAlive()).thenReturn(true); + return new Pair<>(callback, binder); + } + + + @Test + public void testQosCallbackRegistration() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + + final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 = + (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbRegister1); + + final int registerCallbackId = cbRegister1.mQosCallbackId; + mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback); + final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister; + cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbUnregister); + assertEquals(registerCallbackId, cbUnregister.mQosCallbackId); + assertNull(wrapper.getCallbackHistory().poll(200, x -> true)); + } + + @Test + public void testQosCallbackNoRegistrationOnValidationError() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback) + .onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED)); + } + + @Test + public void testQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes( + 1, 2, 3, 4, 5, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER)); + } + + @Test + public void testQosCallbackTooManyRequests() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + for (int i = 0; i < 100; i++) { + final Pair<IQosCallback, IBinder> pair = createQosCallback(); + + try { + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, pair.first); + } catch (ServiceSpecificException e) { + assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS); + if (i < 50) { + fail("TOO_MANY_REQUESTS thrown too early, the count is " + i); + } + + // As long as there is at least 50 requests, it is safe to assume it works. + // Note: The count isn't being tested precisely against 100 because the counter + // is shared with request network. + return; + } + } + fail("TOO_MANY_REQUESTS never thrown"); + } } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 4d151afecd63..52cb836e19c8 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -78,6 +78,7 @@ public class LingerMonitorTest { @Mock Context mCtx; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; + @Mock QosCallbackTracker mQosCallbackTracker; @Before public void setUp() { @@ -358,7 +359,7 @@ public class LingerMonitorTest { NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */, mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE, - Binder.getCallingUid()); + Binder.getCallingUid(), mQosCallbackTracker); nai.everValidated = true; return nai; } diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 25bd7c06be49..1102624da031 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -197,6 +196,11 @@ public class BroadcastInterceptingContext extends ContextWrapper { } @Override + public void sendStickyBroadcast(Intent intent, Bundle options) { + sendBroadcast(intent); + } + + @Override public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { sendBroadcast(intent); } |