Merge "Fix flakiness of testTaskChangeCallBacks"
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 2320b75..ebb0070 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -40,30 +40,77 @@
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.PropertyConfig {
+ 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 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 +118,12 @@
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,6 +134,7 @@
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 @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<java.lang.Void>>);
}
@@ -140,11 +186,15 @@
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 +236,22 @@
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 +295,8 @@
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
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 6fa8f85..814800e 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 @@
public class AppSearchManager {
/**
* The default empty database name.
+ *
* @hide
*/
public static final String DEFAULT_DATABASE_NAME = "";
@@ -70,8 +71,8 @@
/**
* 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 @@
* 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 @@
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 @@
* <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 @@
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
GlobalSearchSession.createGlobalSearchSession(
- mService, mContext.getUserId(), executor, callback);
+ mService, mContext.getUserId(), getPackageName(), executor, callback);
}
/**
@@ -170,40 +176,42 @@
* 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 @@
*
* @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 @@
AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
try {
mService.setSchema(
+ getPackageName(),
DEFAULT_DATABASE_NAME,
schemaBundles,
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
@@ -249,21 +258,19 @@
/**
* 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 @@
}
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 @@
/**
* 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 @@
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 @@
* 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 @@
// 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 @@
});
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 @@
* 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 @@
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 0427577..c4c123c 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 @@
*/
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 @@
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 @@
}
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 @@
* 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}.
@@ -175,6 +183,7 @@
}
try {
mService.setSchema(
+ mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
@@ -206,6 +215,7 @@
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
try {
mService.getSchema(
+ mPackageName,
mDatabaseName,
mUserId,
new IAppSearchResultCallback.Stub() {
@@ -261,7 +271,7 @@
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 +290,7 @@
/**
* 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 +311,13 @@
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 +420,59 @@
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 +492,7 @@
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 +538,8 @@
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 e4e030e..6bb8554 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 @@
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 @@
}
}
- 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 @@
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 2b43777..68ae23f 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 @@
/**
* 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 @@
* {@link AppSearchResult}<{@link Void}>.
*/
void setSchema(
+ in String packageName,
in String databaseName,
in List<Bundle> schemaBundles,
in List<String> schemasNotPlatformSurfaceable,
@@ -54,17 +56,23 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
* 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 @@
* operation.
*/
void query(
+ in String packageName,
in String databaseName,
in String queryExpression,
in Bundle searchSpecBundle,
@@ -123,6 +139,7 @@
* 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 @@
* operation.
*/
void globalQuery(
+ in String packageName,
in String queryExpression,
in Bundle searchSpecBundle,
in int userId,
@@ -156,8 +174,39 @@
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 @@
* 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 @@
/**
* 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 @@
* {@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 0cb0ea4..704509b 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 @@
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 @@
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 @@
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 9ca363e..d394904 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 @@
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 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/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 62cf38b..7f5d202 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 @@
*/
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 @@
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 @@
}
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 @@
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 @@
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,41 @@
}
/**
+ * 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 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
+ */
+ // TODO(b/177266929) link to Migrator in once it's ready.
+ @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 +202,7 @@
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 +210,15 @@
}
/**
- * 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 +239,31 @@
@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 +292,97 @@
/** 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 +397,7 @@
@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,54 +432,16 @@
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;
-
- 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 logical schema-type of the contents of this property.
- *
- * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}. Otherwise,
- * it is {@code null}.
- */
- @Nullable
- public String getSchemaType() {
- return mBundle.getString(SCHEMA_TYPE_FIELD);
- }
-
- /**
- * Returns the cardinality of the property (whether it is optional, required or repeated).
- */
- public @Cardinality int getCardinality() {
- return mBundle.getInt(CARDINALITY_FIELD, -1);
+ StringPropertyConfig(@NonNull Bundle bundle) {
+ super(bundle);
}
/** Returns how the property is indexed. */
@@ -351,75 +454,19 @@
return mBundle.getInt(TOKENIZER_TYPE_FIELD);
}
- @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;
- }
-
/**
- * Builder for {@link PropertyConfig}.
+ * Builder for {@link StringPropertyConfig}.
*
- * <p>The following properties must be set, or {@link PropertyConfig} construction will
- * fail:
- *
- * <ul>
- * <li>dataType
- * <li>cardinality
- * </ul>
- *
- * <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 StringPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mBundle.putString(NAME_FIELD, propertyName);
- }
-
- /**
- * Type of data the property contains (e.g. string, int, bytes, etc).
- *
- * <p>This property must be set.
- */
- @NonNull
- public PropertyConfig.Builder setDataType(@DataType int dataType) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkArgumentInRange(
- dataType, DATA_TYPE_STRING, DATA_TYPE_DOCUMENT, "dataType");
- mBundle.putInt(DATA_TYPE_FIELD, dataType);
- return this;
- }
-
- /**
- * 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.
- */
- @NonNull
- public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemaType);
- mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
- return this;
+ mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING);
}
/**
@@ -427,8 +474,9 @@
*
* <p>This property must be set.
*/
+ @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
- public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
+ public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
@@ -440,7 +488,7 @@
* Configures how a property should be indexed so that it can be retrieved by queries.
*/
@NonNull
- public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
+ 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");
@@ -450,7 +498,7 @@
/** Configures how this property should be tokenized (split into words). */
@NonNull
- public PropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
+ 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");
@@ -459,6 +507,337 @@
}
/**
+ * 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);
+ }
+ }
+ }
+
+ /** Configuration for a property containing a 64-bit integer. */
+ public static final class Int64PropertyConfig extends PropertyConfig {
+ Int64PropertyConfig(@NonNull Bundle bundle) {
+ super(bundle);
+ }
+
+ /**
+ * Builder for {@link Int64PropertyConfig}.
+ *
+ * <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 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);
+ }
+
+ /**
+ * Builder for {@link DoublePropertyConfig}.
+ *
+ * <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 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);
+ }
+ }
+ }
+
+ /** Configuration for a property containing a boolean. */
+ public static final class BooleanPropertyConfig extends PropertyConfig {
+ BooleanPropertyConfig(@NonNull Bundle bundle) {
+ super(bundle);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+ }
+
+ /** Configuration for a property containing a byte array. */
+ public static final class BytesPropertyConfig extends PropertyConfig {
+ BytesPropertyConfig(@NonNull Bundle bundle) {
+ super(bundle);
+ }
+
+ /**
+ * Builder for {@link BytesPropertyConfig}.
+ *
+ * <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 BytesPropertyConfig.Builder}. */
+ public Builder(@NonNull String propertyName) {
+ mBundle.putString(NAME_FIELD, propertyName);
+ mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES);
+ }
+
+ /**
+ * 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 BytesPropertyConfig.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 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>This property must be set.
+ */
+ @NonNull
+ public DocumentPropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(schemaType);
+ mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
+ return this;
+ }
+
+ /**
+ * 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 DocumentPropertyConfig.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 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 DocumentPropertyConfig.Builder setIndexNestedProperties(
+ boolean indexNestedProperties) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, indexNestedProperties);
+ return this;
+ }
+
+ /**
* Constructs a new {@link PropertyConfig} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
@@ -467,24 +846,16 @@
* 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);
}
}
}
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 11e7fab..2f02808 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 @@
/**
* 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 74afdd2..38e0046 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 @@
* @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 @@
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 @@
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 0000000..2bfcf28
--- /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 62324b2..d792f45 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 @@
* 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 b5d5f04d..963062c 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 @@
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 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 @@
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/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 1f1e9a1..ec41353 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,29 @@
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;
public AppSearchManagerService(Context context) {
super(context);
@@ -59,11 +62,13 @@
@Override
public void onStart() {
publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
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 +76,7 @@
boolean forceOverride,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
+ Preconditions.checkNotNull(packageName);
Preconditions.checkNotNull(databaseName);
Preconditions.checkNotNull(schemaBundles);
Preconditions.checkNotNull(callback);
@@ -78,6 +84,7 @@
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)));
@@ -94,7 +101,6 @@
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
}
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- String packageName = convertUidToPackageName(callingUid);
impl.setSchema(
packageName,
databaseName,
@@ -102,8 +108,8 @@
schemasNotPlatformSurfaceable,
schemasPackageAccessible,
forceOverride);
- invokeCallbackOnResult(callback,
- AppSearchResult.newSuccessfulResult(/*result=*/ null));
+ invokeCallbackOnResult(
+ callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
} finally {
@@ -113,24 +119,26 @@
@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 {
+ verifyCallingPackage(callingUid, packageName);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- String packageName = convertUidToPackageName(callingUid);
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 +148,12 @@
@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 +161,10 @@
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);
for (int i = 0; i < documentBundles.size(); i++) {
GenericDocument document = new GenericDocument(documentBundles.get(i));
try {
@@ -176,11 +186,14 @@
@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 +202,15 @@
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);
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 +227,13 @@
// 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 +242,16 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
+ verifyCallingPackage(callingUid, packageName);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- String packageName = convertUidToPackageName(callingUid);
- SearchResultPage searchResultPage = impl.query(
- packageName,
- databaseName,
- queryExpression,
- new SearchSpec(searchSpecBundle));
- invokeCallbackOnResult(callback,
+ SearchResultPage searchResultPage =
+ impl.query(
+ packageName,
+ databaseName,
+ queryExpression,
+ new SearchSpec(searchSpecBundle));
+ invokeCallbackOnResult(
+ callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
@@ -245,10 +262,12 @@
@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 +275,15 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
+ verifyCallingPackage(callingUid, packageName);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), 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 +293,9 @@
}
@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();
@@ -281,7 +306,8 @@
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
SearchResultPage searchResultPage = impl.getNextPage(nextPageToken);
- invokeCallbackOnResult(callback,
+ invokeCallbackOnResult(
+ callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
@@ -306,12 +332,47 @@
}
@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 = ImplInstanceManager.getInstance(getContext(), 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 +380,15 @@
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);
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 +403,13 @@
@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 +418,12 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
+ verifyCallingPackage(callingUid, packageName);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- String packageName = convertUidToPackageName(callingUid);
- impl.removeByQuery(packageName, databaseName, queryExpression,
+ impl.removeByQuery(
+ packageName,
+ databaseName,
+ queryExpression,
new SearchSpec(searchSpecBundle));
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
@@ -398,28 +464,22 @@
}
}
- /**
- * 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 +488,8 @@
}
/** 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 +515,8 @@
*
* <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 +525,18 @@
}
}
- //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 2871eb6..cd90f85 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -69,7 +69,7 @@
private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
throws AppSearchException {
File appSearchDir = getAppSearchDir(context, userId);
- return AppSearchImpl.create(appSearchDir);
+ return AppSearchImpl.create(appSearchDir, context, /*globalQuerierPackage=*/"");
}
private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
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 674f199..1665b1c 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,12 @@
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.exceptions.AppSearchException;
+import android.content.Context;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,6 +39,7 @@
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
+import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
import com.google.android.icing.IcingSearchEngine;
import com.google.android.icing.proto.DeleteByQueryResultProto;
@@ -54,6 +57,7 @@
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 +68,7 @@
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 +159,20 @@
* folder.
*/
@NonNull
- public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException {
+ public static AppSearchImpl create(
+ @NonNull File icingDir, @NonNull Context context, @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, globalQuerierPackage);
appSearchImpl.initializeVisibilityStore();
return appSearchImpl;
}
- private AppSearchImpl(@NonNull File icingDir) throws AppSearchException {
+ private AppSearchImpl(
+ @NonNull File icingDir, @NonNull Context context, @NonNull String globalQuerierPackage)
+ throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
@@ -173,7 +184,7 @@
.build();
mIcingSearchEngineLocked = new IcingSearchEngine(options);
- mVisibilityStoreLocked = new VisibilityStore(this);
+ mVisibilityStoreLocked = new VisibilityStore(this, context, globalQuerierPackage);
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
SchemaProto schemaProto;
@@ -419,6 +430,8 @@
* @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 +440,35 @@
@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 +508,12 @@
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 +529,72 @@
*
* @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;
- }
-
- // Otherwise, include this prefix in our global search.
- prefixes.add(prefix);
}
- return doQueryLocked(prefixes, queryExpression, searchSpec);
+ // 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);
+ }
+ }
+ }
+ }
+ }
+
+ return doQueryLocked(
+ prefixFilters, allowedPrefixedSchemas, queryExpression, searchSpec);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -535,28 +603,25 @@
@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 +672,31 @@
}
}
+ /** 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 +757,14 @@
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());
@@ -904,19 +996,23 @@
}
/**
- * 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 +1022,28 @@
return false;
}
- // Cache the schema type filters and namespaces before clearing everything.
- List<String> schemaTypeFilters = searchSpecBuilder.getSchemaTypeFiltersList();
- searchSpecBuilder.clearSchemaTypeFilters();
+ 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 +1063,70 @@
}
/**
+ * 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
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
index a940ec1..24d64a0 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
@@ -22,10 +22,14 @@
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.util.ArrayMap;
import android.util.ArraySet;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -55,7 +59,7 @@
*/
class VisibilityStore {
/** 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 +85,14 @@
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,22 +106,20 @@
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();
@@ -148,6 +149,12 @@
private final AppSearchImpl mAppSearchImpl;
+ private final Context mContext;
+
+ // UID of the package that has platform-query privileges, i.e. can query for all
+ // platform-surfaceable content.
+ private int mGlobalQuerierPackageUid;
+
/**
* Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas
* in the map are prefixed.
@@ -173,8 +180,20 @@
*
* @param appSearchImpl AppSearchImpl instance
*/
- VisibilityStore(@NonNull AppSearchImpl appSearchImpl) {
+ VisibilityStore(
+ @NonNull AppSearchImpl appSearchImpl,
+ @NonNull Context context,
+ @NonNull String globalQuerierPackage) {
mAppSearchImpl = appSearchImpl;
+ mContext = context;
+ mGlobalQuerierPackageUid = Process.INVALID_UID;
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ // This should always pass since we should only allow platform access on S+ (the first
+ // version that AppSearch is offered on).
+ mGlobalQuerierPackageUid =
+ Api24Impl.getGlobalQuerierPackageUid(context, globalQuerierPackage);
+ }
}
/**
@@ -216,7 +235,8 @@
PACKAGE_NAME,
DATABASE_NAME,
NAMESPACE,
- /*uri=*/ addUriPrefix(prefix));
+ /*uri=*/ addUriPrefix(prefix),
+ /*typePropertyPaths=*/ Collections.emptyMap());
// Update platform visibility settings
String[] schemas =
@@ -302,8 +322,7 @@
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 +351,96 @@
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);
- Set<String> notPlatformSurfaceableSchemas = mNotPlatformSurfaceableMap.get(prefix);
- if (notPlatformSurfaceableSchemas == null) {
+
+ if (callerUid == mGlobalQuerierPackageUid
+ && 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);
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) {
+ // PackageManager.hasSigningCertificate is only available on P+
+ // This should never fail since we should only allow package access on S+ (the first
+ // version that AppSearch is offered on). But just in case, default to no package
+ // access.
+ return false;
+ }
+
+ for (PackageIdentifier packageIdentifier : packageIdentifiers) {
+ // Check that the caller uid matches this allowlisted PackageIdentifier.
+ if (Api24Impl.getPackageUid(mContext, packageIdentifier.getPackageName())
+ != callerUid) {
+ continue;
+ }
+
+ // Check that the package also has the matching certificate
+ if (Api28Impl.hasSigningCertificate(
+ mContext,
+ packageIdentifier.getPackageName(),
+ packageIdentifier.getSha256Certificate())) {
+ // 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;
}
/**
@@ -389,4 +462,85 @@
private static String addUriPrefix(String uri) {
return URI_PREFIX + uri;
}
+
+ /**
+ * Wrapper class around API 24 methods.
+ *
+ * <p>Even though wrapping a call to a method from an API above minSdk inside an SDK_INT check
+ * makes it runtime safe, it is not optimal. When ART tries to optimize a class, it will do so
+ * regardless of the execution path, and will fail if it tries to resolve a method at a higher
+ * API if that method is being referenced somewhere in the class, even if that method would
+ * never be called at runtime due to the SDK_INT check. ART will however only try to optimize a
+ * class the first time it's referenced at runtime, this means if we wrap our above minSdk
+ * method calls inside classes that are only referenced at runtime at the appropriate API level,
+ * then we guarantee the ability to resolve all the methods.
+ */
+ @RequiresApi(24)
+ private static class Api24Impl {
+ private Api24Impl() {}
+
+ /**
+ * 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.
+ */
+ static int getGlobalQuerierPackageUid(
+ @NonNull Context context, @NonNull String globalQuerierPackage) {
+ try {
+ // TODO(b/169883602): In framework, this should be UserHandle.isSameApp or
+ // packageManager.getPackageUidAsUser().
+ int flags =
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY;
+ return context.getPackageManager().getPackageUid(globalQuerierPackage, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Global querier doesn't exist.
+ }
+ return Process.INVALID_UID;
+ }
+
+ /**
+ * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable
+ * to find the UID.
+ */
+ static int getPackageUid(@NonNull Context context, @NonNull String packageName) {
+ try {
+ // TODO(b/169883602): In framework, this should be UserHandle.isSameApp or
+ // packageManager.getPackageUidAsUser().
+ return context.getPackageManager().getPackageUid(packageName, /*flags=*/ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Global querier doesn't exist.
+ }
+ return Process.INVALID_UID;
+ }
+ }
+
+ /**
+ * Wrapper class around API 28 methods.
+ *
+ * <p>Even though wrapping a call to a method from an API above minSdk inside an SDK_INT check
+ * makes it runtime safe, it is not optimal. When ART tries to optimize a class, it will do so
+ * regardless of the execution path, and will fail if it tries to resolve a method at a higher
+ * API if that method is being referenced somewhere in the class, even if that method would
+ * never be called at runtime due to the SDK_INT check. ART will however only try to optimize a
+ * class the first time it's referenced at runtime, this means if we wrap our above minSdk
+ * method calls inside classes that are only referenced at runtime at the appropriate API level,
+ * then we guarantee the ability to resolve all the methods.
+ */
+ @RequiresApi(28)
+ private static class Api28Impl {
+ private Api28Impl() {}
+
+ /**
+ * Returns whether the {@code packageName} has been signed with {@code sha256Certificate}.
+ */
+ static boolean hasSigningCertificate(
+ @NonNull Context context,
+ @NonNull String packageName,
+ @NonNull byte[] sha256Certificate) {
+ return context.getPackageManager()
+ .hasSigningCertificate(
+ packageName, sha256Certificate, PackageManager.CERT_INPUT_SHA256);
+ }
+ }
}
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 4165af3..ce1c9f4 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 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 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 @@
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 @@
}
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 @@
}
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);
+ 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);
- // 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);
+ } 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 @@
@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 @@
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 0d7d3e1..07d50ae 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.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 @@
@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 @@
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/TypePropertyPathToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
new file mode 100644
index 0000000..6f6dad2
--- /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 9be3049..5ab3450 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-Icd58005ad659b6b3d03f683f8954939175324685
+Iff96eae150c7cdd281c9ecb5d93f4ef697e89f1a
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 b0478d5..c2c1d7c 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,6 +28,7 @@
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;
@@ -129,6 +130,14 @@
@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();
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 3e81968..ff91f59 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
@@ -175,6 +175,25 @@
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.
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 459fd151..13858a3 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
@@ -28,6 +28,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Future;
public class AppSearchTestUtils {
@@ -59,6 +60,20 @@
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/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 7aed32c..c9427e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2850,7 +2850,7 @@
}
final int appId = UserHandle.getAppId(uid);
updateTempWhitelistAppIdsLocked(uid, false, 0, 0);
- mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, appId, 0)
+ mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS, appId, 0)
.sendToTarget();
reportTempWhitelistChangedLocked(uid, false);
try {
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 13c4953..24c9e78 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -237,7 +237,7 @@
*/
@NonNull
public List<String> getUnsupportedVideoMimeTypes() {
- return new ArrayList<>(mSupportedVideoMimeTypes);
+ return new ArrayList<>(mUnsupportedVideoMimeTypes);
}
/*
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index 07221f9..14ebb71 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -62,4 +62,13 @@
// Create a symlink from app_process to app_process32 or 64
// depending on the target configuration.
symlink_preferred_arch: true,
+
+ // Enable ASYNC MTE in the zygote, in order to allow apps and the system
+ // server to use MTE. We use ASYNC because we don't expect the pre-fork
+ // zygote to have substantial memory corruption bugs (as it's primarily Java
+ // code), and we don't want to waste memory recording malloc/free stack
+ // traces (which happens in SYNC mode).
+ sanitize: {
+ memtag_heap: true,
+ },
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 27660ec..b8327a9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5573,6 +5573,7 @@
field public static final String EXTRA_PROGRESS = "android.progress";
field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
@@ -5712,12 +5713,13 @@
public static class Notification.BigPictureStyle extends android.app.Notification.Style {
ctor public Notification.BigPictureStyle();
ctor @Deprecated public Notification.BigPictureStyle(android.app.Notification.Builder);
- method public android.app.Notification.BigPictureStyle bigLargeIcon(android.graphics.Bitmap);
- method public android.app.Notification.BigPictureStyle bigLargeIcon(android.graphics.drawable.Icon);
- method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap);
+ method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.Bitmap);
+ method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.Bitmap);
method @NonNull public android.app.Notification.BigPictureStyle bigPictureContentDescription(@Nullable CharSequence);
- method public android.app.Notification.BigPictureStyle setBigContentTitle(CharSequence);
- method public android.app.Notification.BigPictureStyle setSummaryText(CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle setBigContentTitle(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle setSummaryText(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle showBigPictureWhenCollapsed(boolean);
}
public static class Notification.BigTextStyle extends android.app.Notification.Style {
@@ -7191,6 +7193,7 @@
field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+ field public static final String EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY = "android.app.extra.DEVICE_PASSWORD_REQUIREMENT_ONLY";
field @RequiresPermission(android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY";
field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -21520,6 +21523,8 @@
method public void removeKeys(@NonNull byte[]);
method public void removeOfflineLicense(@NonNull byte[]);
method public void removeSecureStop(@NonNull byte[]);
+ method public boolean requiresSecureDecoder(@NonNull String);
+ method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int);
method public void restoreKeys(@NonNull byte[], @NonNull byte[]);
method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener);
method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler);
@@ -31276,6 +31281,7 @@
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();
@@ -31284,6 +31290,7 @@
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);
}
@@ -31810,6 +31817,9 @@
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 {
}
@@ -33012,6 +33022,7 @@
field public static final String CACHED_PHOTO_URI = "photo_uri";
field public static final String CALL_SCREENING_APP_NAME = "call_screening_app_name";
field public static final String CALL_SCREENING_COMPONENT_NAME = "call_screening_component_name";
+ field public static final String COMPOSER_PHOTO_URI = "composer_photo_uri";
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
@@ -33036,6 +33047,7 @@
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
+ field public static final String LOCATION = "location";
field public static final String MISSED_REASON = "missed_reason";
field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L
field public static final int MISSED_TYPE = 3; // 0x3
@@ -33051,7 +33063,11 @@
field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
field public static final int PRESENTATION_UNKNOWN = 3; // 0x3
+ field public static final String PRIORITY = "priority";
+ field public static final int PRIORITY_NORMAL = 0; // 0x0
+ field public static final int PRIORITY_URGENT = 1; // 0x1
field public static final int REJECTED_TYPE = 5; // 0x5
+ field public static final String SUBJECT = "subject";
field public static final String TRANSCRIPTION = "transcription";
field public static final String TYPE = "type";
field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L
@@ -33066,6 +33082,15 @@
field public static final String VOICEMAIL_URI = "voicemail_uri";
}
+ public static class CallLog.Locations implements android.provider.BaseColumns {
+ field public static final String AUTHORITY = "call_composer_locations";
+ field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_composer_location";
+ field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_composer_location";
+ field @NonNull public static final android.net.Uri CONTENT_URI;
+ field public static final String LATITUDE = "latitude";
+ field public static final String LONGITUDE = "longitude";
+ }
+
@Deprecated public class Contacts {
field @Deprecated public static final String AUTHORITY = "contacts";
field @Deprecated public static final android.net.Uri CONTENT_URI;
@@ -39022,7 +39047,9 @@
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onStopRtt();
+ method public void onTrackedByNonUiService(boolean);
method public void onUnhold();
+ method public void onUsingAlternativeUi(boolean);
method public static String propertiesToString(int);
method public final void putExtras(@NonNull android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
@@ -39361,6 +39388,7 @@
field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
field public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 256; // 0x100
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
+ field public static final String EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE = "android.telecom.extra.ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE";
field public static final String EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE = "android.telecom.extra.ALWAYS_USE_VOIP_AUDIO_MODE";
field public static final String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING = "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
field public static final String EXTRA_CALL_SUBJECT_MAX_LENGTH = "android.telecom.extra.CALL_SUBJECT_MAX_LENGTH";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a7602f8..5f61472 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -136,6 +136,7 @@
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";
@@ -2344,6 +2345,7 @@
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
+ field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
@@ -4415,7 +4417,8 @@
method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle);
method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String, @Nullable String);
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
@@ -7329,157 +7332,157 @@
package android.net.metrics {
- public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
}
- public static final class ApfProgramEvent.Builder {
- ctor public ApfProgramEvent.Builder();
- method @NonNull public android.net.metrics.ApfProgramEvent build();
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
- method @NonNull public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
+ @Deprecated public static final class ApfProgramEvent.Builder {
+ ctor @Deprecated public ApfProgramEvent.Builder();
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent build();
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
}
- public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
}
- public static final class ApfStats.Builder {
- ctor public ApfStats.Builder();
- method @NonNull public android.net.metrics.ApfStats build();
- method @NonNull public android.net.metrics.ApfStats.Builder setDroppedRas(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setDurationMs(long);
- method @NonNull public android.net.metrics.ApfStats.Builder setMatchingRas(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setParseErrors(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setReceivedRas(int);
- method @NonNull public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
+ @Deprecated public static final class ApfStats.Builder {
+ ctor @Deprecated public ApfStats.Builder();
+ method @Deprecated @NonNull public android.net.metrics.ApfStats build();
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setDroppedRas(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setDurationMs(long);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setMatchingRas(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setParseErrors(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setReceivedRas(int);
+ method @Deprecated @NonNull public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
}
- public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
}
- public static final class DhcpClientEvent.Builder {
- ctor public DhcpClientEvent.Builder();
- method @NonNull public android.net.metrics.DhcpClientEvent build();
- method @NonNull public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
- method @NonNull public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
+ @Deprecated public static final class DhcpClientEvent.Builder {
+ ctor @Deprecated public DhcpClientEvent.Builder();
+ method @Deprecated @NonNull public android.net.metrics.DhcpClientEvent build();
+ method @Deprecated @NonNull public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
+ method @Deprecated @NonNull public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
}
- public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
- ctor public DhcpErrorEvent(int);
- method public static int errorCodeWithOption(int, int);
- field public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
- field public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
- field public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
- field public static final int DHCP_ERROR = 4; // 0x4
- field public static final int DHCP_INVALID_OPTION_LENGTH = 67305472; // 0x4030000
- field public static final int DHCP_NO_COOKIE = 67502080; // 0x4060000
- field public static final int DHCP_NO_MSG_TYPE = 67371008; // 0x4040000
- field public static final int DHCP_UNKNOWN_MSG_TYPE = 67436544; // 0x4050000
- field public static final int L2_ERROR = 1; // 0x1
- field public static final int L2_TOO_SHORT = 16842752; // 0x1010000
- field public static final int L2_WRONG_ETH_TYPE = 16908288; // 0x1020000
- field public static final int L3_ERROR = 2; // 0x2
- field public static final int L3_INVALID_IP = 33751040; // 0x2030000
- field public static final int L3_NOT_IPV4 = 33685504; // 0x2020000
- field public static final int L3_TOO_SHORT = 33619968; // 0x2010000
- field public static final int L4_ERROR = 3; // 0x3
- field public static final int L4_NOT_UDP = 50397184; // 0x3010000
- field public static final int L4_WRONG_PORT = 50462720; // 0x3020000
- field public static final int MISC_ERROR = 5; // 0x5
- field public static final int PARSING_ERROR = 84082688; // 0x5030000
- field public static final int RECEIVE_ERROR = 84017152; // 0x5020000
+ @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor @Deprecated public DhcpErrorEvent(int);
+ method @Deprecated public static int errorCodeWithOption(int, int);
+ field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
+ field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
+ field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
+ field @Deprecated public static final int DHCP_ERROR = 4; // 0x4
+ field @Deprecated public static final int DHCP_INVALID_OPTION_LENGTH = 67305472; // 0x4030000
+ field @Deprecated public static final int DHCP_NO_COOKIE = 67502080; // 0x4060000
+ field @Deprecated public static final int DHCP_NO_MSG_TYPE = 67371008; // 0x4040000
+ field @Deprecated public static final int DHCP_UNKNOWN_MSG_TYPE = 67436544; // 0x4050000
+ field @Deprecated public static final int L2_ERROR = 1; // 0x1
+ field @Deprecated public static final int L2_TOO_SHORT = 16842752; // 0x1010000
+ field @Deprecated public static final int L2_WRONG_ETH_TYPE = 16908288; // 0x1020000
+ field @Deprecated public static final int L3_ERROR = 2; // 0x2
+ field @Deprecated public static final int L3_INVALID_IP = 33751040; // 0x2030000
+ field @Deprecated public static final int L3_NOT_IPV4 = 33685504; // 0x2020000
+ field @Deprecated public static final int L3_TOO_SHORT = 33619968; // 0x2010000
+ field @Deprecated public static final int L4_ERROR = 3; // 0x3
+ field @Deprecated public static final int L4_NOT_UDP = 50397184; // 0x3010000
+ field @Deprecated public static final int L4_WRONG_PORT = 50462720; // 0x3020000
+ field @Deprecated public static final int MISC_ERROR = 5; // 0x5
+ field @Deprecated public static final int PARSING_ERROR = 84082688; // 0x5030000
+ field @Deprecated public static final int RECEIVE_ERROR = 84017152; // 0x5020000
}
- public class IpConnectivityLog {
- ctor public IpConnectivityLog();
- method public boolean log(long, @NonNull android.net.metrics.IpConnectivityLog.Event);
- method public boolean log(@NonNull String, @NonNull android.net.metrics.IpConnectivityLog.Event);
- method public boolean log(@NonNull android.net.Network, @NonNull int[], @NonNull android.net.metrics.IpConnectivityLog.Event);
- method public boolean log(int, @NonNull int[], @NonNull android.net.metrics.IpConnectivityLog.Event);
- method public boolean log(@NonNull android.net.metrics.IpConnectivityLog.Event);
+ @Deprecated public class IpConnectivityLog {
+ ctor @Deprecated public IpConnectivityLog();
+ method @Deprecated public boolean log(long, @NonNull android.net.metrics.IpConnectivityLog.Event);
+ method @Deprecated public boolean log(@NonNull String, @NonNull android.net.metrics.IpConnectivityLog.Event);
+ method @Deprecated public boolean log(@NonNull android.net.Network, @NonNull int[], @NonNull android.net.metrics.IpConnectivityLog.Event);
+ method @Deprecated public boolean log(int, @NonNull int[], @NonNull android.net.metrics.IpConnectivityLog.Event);
+ method @Deprecated public boolean log(@NonNull android.net.metrics.IpConnectivityLog.Event);
}
- public static interface IpConnectivityLog.Event extends android.os.Parcelable {
+ @Deprecated public static interface IpConnectivityLog.Event extends android.os.Parcelable {
}
- public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
- ctor public IpManagerEvent(int, long);
- field public static final int COMPLETE_LIFECYCLE = 3; // 0x3
- field public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
- field public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
- field public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
- field public static final int ERROR_STARTING_IPV4 = 4; // 0x4
- field public static final int ERROR_STARTING_IPV6 = 5; // 0x5
- field public static final int PROVISIONING_FAIL = 2; // 0x2
- field public static final int PROVISIONING_OK = 1; // 0x1
+ @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor @Deprecated public IpManagerEvent(int, long);
+ field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
+ field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
+ field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
+ field @Deprecated public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
+ field @Deprecated public static final int ERROR_STARTING_IPV4 = 4; // 0x4
+ field @Deprecated public static final int ERROR_STARTING_IPV6 = 5; // 0x5
+ field @Deprecated public static final int PROVISIONING_FAIL = 2; // 0x2
+ field @Deprecated public static final int PROVISIONING_OK = 1; // 0x1
}
- public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
- ctor public IpReachabilityEvent(int);
- field public static final int NUD_FAILED = 512; // 0x200
- field public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
- field public static final int PROBE = 256; // 0x100
- field public static final int PROVISIONING_LOST = 768; // 0x300
- field public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
+ @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor @Deprecated public IpReachabilityEvent(int);
+ field @Deprecated public static final int NUD_FAILED = 512; // 0x200
+ field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
+ field @Deprecated public static final int PROBE = 256; // 0x100
+ field @Deprecated public static final int PROVISIONING_LOST = 768; // 0x300
+ field @Deprecated public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
}
- public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
- ctor public NetworkEvent(int, long);
- ctor public NetworkEvent(int);
- field public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
- field public static final int NETWORK_CONNECTED = 1; // 0x1
- field public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
- field public static final int NETWORK_DISCONNECTED = 7; // 0x7
- field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
- field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
- field public static final int NETWORK_LINGER = 5; // 0x5
- field public static final int NETWORK_PARTIAL_CONNECTIVITY = 13; // 0xd
- field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
- field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
- field public static final int NETWORK_UNLINGER = 6; // 0x6
- field public static final int NETWORK_VALIDATED = 2; // 0x2
- field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
+ @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor @Deprecated public NetworkEvent(int, long);
+ ctor @Deprecated public NetworkEvent(int);
+ field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
+ field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
+ field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
+ field @Deprecated public static final int NETWORK_DISCONNECTED = 7; // 0x7
+ field @Deprecated public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
+ field @Deprecated public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
+ field @Deprecated public static final int NETWORK_LINGER = 5; // 0x5
+ field @Deprecated public static final int NETWORK_PARTIAL_CONNECTIVITY = 13; // 0xd
+ field @Deprecated public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
+ field @Deprecated public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
+ field @Deprecated public static final int NETWORK_UNLINGER = 6; // 0x6
+ field @Deprecated public static final int NETWORK_VALIDATED = 2; // 0x2
+ field @Deprecated public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
}
- public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
}
- public static final class RaEvent.Builder {
- ctor public RaEvent.Builder();
- method @NonNull public android.net.metrics.RaEvent build();
- method @NonNull public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
- method @NonNull public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
- method @NonNull public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
- method @NonNull public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
- method @NonNull public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
- method @NonNull public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+ @Deprecated public static final class RaEvent.Builder {
+ ctor @Deprecated public RaEvent.Builder();
+ method @Deprecated @NonNull public android.net.metrics.RaEvent build();
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+ method @Deprecated @NonNull public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
}
- public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
- method @NonNull public static String getProbeName(int);
- field public static final int DNS_FAILURE = 0; // 0x0
- field public static final int DNS_SUCCESS = 1; // 0x1
- field public static final int PROBE_DNS = 0; // 0x0
- field public static final int PROBE_FALLBACK = 4; // 0x4
- field public static final int PROBE_HTTP = 1; // 0x1
- field public static final int PROBE_HTTPS = 2; // 0x2
- field public static final int PROBE_PAC = 3; // 0x3
- field public static final int PROBE_PRIVDNS = 5; // 0x5
+ @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated @NonNull public static String getProbeName(int);
+ field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
+ field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
+ field @Deprecated public static final int PROBE_DNS = 0; // 0x0
+ field @Deprecated public static final int PROBE_FALLBACK = 4; // 0x4
+ field @Deprecated public static final int PROBE_HTTP = 1; // 0x1
+ field @Deprecated public static final int PROBE_HTTPS = 2; // 0x2
+ field @Deprecated public static final int PROBE_PAC = 3; // 0x3
+ field @Deprecated public static final int PROBE_PRIVDNS = 5; // 0x5
}
- public static final class ValidationProbeEvent.Builder {
- ctor public ValidationProbeEvent.Builder();
- method @NonNull public android.net.metrics.ValidationProbeEvent build();
- method @NonNull public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
- method @NonNull public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
- method @NonNull public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
+ @Deprecated public static final class ValidationProbeEvent.Builder {
+ ctor @Deprecated public ValidationProbeEvent.Builder();
+ method @Deprecated @NonNull public android.net.metrics.ValidationProbeEvent build();
+ method @Deprecated @NonNull public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
+ method @Deprecated @NonNull public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
+ method @Deprecated @NonNull public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
}
}
@@ -7752,7 +7755,10 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAlwaysOnEnabled();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setAlwaysOn(boolean);
method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
}
@@ -9742,6 +9748,7 @@
method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
method public void onAllowedAdjustmentsChanged();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public void onNotificationClicked(@NonNull String);
method public void onNotificationDirectReplied(@NonNull String);
method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9e83136..870ab83 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -292,6 +292,7 @@
}
public class StatusBarManager {
+ method public void clickNotification(@Nullable String, int, int, boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
method public void expandNotificationsPanel();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
@@ -727,6 +728,11 @@
method @AnyThread @NonNull public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int);
}
+ public final class Rect implements android.os.Parcelable {
+ method public void splitHorizontally(@NonNull android.graphics.Rect...);
+ method public void splitVertically(@NonNull android.graphics.Rect...);
+ }
+
}
package android.graphics.drawable {
@@ -1182,7 +1188,7 @@
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);
+ 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 class EthernetManager {
@@ -2560,6 +2566,7 @@
method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 986051c..9b5a1dd 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -532,4 +532,9 @@
*/
@Nullable
public abstract Intent getIntentForIntentSender(IIntentSender sender);
+
+ /**
+ * @return mBootTimeTempAllowlistDuration of ActivityManagerConstants.
+ */
+ public abstract long getBootTimeTempAllowListDuration();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bd437f4..e5c34c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -64,12 +64,14 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
}
private void handleStopService(IBinder token) {
+ mServicesData.remove(token);
Service s = mServices.remove(token);
if (s != null) {
try {
@@ -5026,7 +5035,7 @@
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 @@
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/AppOps.md b/core/java/android/app/AppOps.md
index b827a0d..4589a71 100644
--- a/core/java/android/app/AppOps.md
+++ b/core/java/android/app/AppOps.md
@@ -231,7 +231,107 @@
As each runtime permission has an associated app-op this API is particularly useful for an app
that want to find unexpected private data accesses.
-### Tracking the last accesses via an API
+#### Implementation
+
+The goal is to trigger a callback to `AppOpsManager.OnOpNotedCallback` any time a data provider
+declares that data was sent to the app (i.e. calls `AppOpsManager.noteOp`). There are four cases
+
+##### Synchronous data accesses
+
+This is the case where the client calls an API and the data is sent back as the return value of this
+API call. E.g. `LocationManager.getLastKnownLocation` returns the last known location as the return
+value of the method call.
+
+In this case
+1. The client calls into a Android API in the Android framework, e.g. `LocationManager`
+2. The framework code calls via a `Binder` call into the data provider, e.g. the
+`LocationManagerService` residing in the system server.
+3. Somewhere in the data provider the data provider calls `AppOpsManager.noteOp` and thereby
+declares that data was accessed. This data access is recorded in
+`AppOpsManager.sAppOpsNotedInThisBinderTransaction`
+4. When the binder call returns the RPC code (`Binder`/`Parcel`) calls
+`AppOpsManager.readAndLogNotedAppops` which checks is the binder return value contained any
+prefix indicating that data was accessed. If so the RPC code calls `onNoted` on the the currently
+registered `OnOpNotedCallback`.
+5. The rest of the implementation is up to the client, but one to use the callbacks is for the
+client to take a stack trace in the `onNoted` implementation. This stack trace allows to pin point
+where in the app's code the data access came from.
+
+
+
+In above graphics you can see that
+1. an app (`com.app.A`, red) is calling into the android framework
+(blue).
+1. The call triggers a RPC call into the data provider (green).
+1. The data provider calls `AppOpsManager.noteOp` (first star)
+1. On the return from the RPC call the framework code (second star) realizes that there was a data
+access and calls `OnOpNotedCallback.onNoted`.
+1. If at this time the code in onNoted would take a stack trace it would get what is in the gray
+box, i.e.
+```
+com.app.A.a
+- com.app.A.b
+ - com.app.A.c
+ - com.app.A.d
+ - android...Manager
+ - several android internal RPC methods
+ - com.app.B.onNoted (extends OnOpNotedCallback.onNoted)
+```
+
+As `onNoted` also reports the attributionTag and the noted op the app can now build a mapping
+indicating what code accesses what private data.
+
+##### Self data accesses
+
+This is similar to the [synchronous data access](#synchronous-data-accesses) case only that the data
+provider and client are in the same process. In this case Android's RPC code is no involved and
+`AppOpsManager.noteOp` directly triggers `OnOpNotedCallback.onSelfNoted`. This should be a uncommon
+case as it is uncommon for an app to provide data, esp. to itself.
+
+If an app takes above suggestion and collects stack traces for synchronous accesses self-accesses
+can be treated in the same way.
+
+##### Async data accesses
+
+There are cases where the data access is not directly triggered via an API. E.g.
+`LocationManager.requestLocationUpdates(listener)` registers a callback. Once the location subsystem
+determines a location it calls the registered listener with the data. There can be quite significant
+time between registering the listener and getting the data. In some cases (e.g. Geo-fencing) it
+might take days and the app registering for the data and the app receiving the data might not even
+be the same process or even version.
+
+Hence above suggestion with taking the stack trace to determine what triggered the data access does
+not work. In this case it is recommended for data providers to come up with a way to help the app
+developer understand why a data access is triggered. E.g. in the case of
+`LocationManager.requestLocationUpdates(listener)` the data provider is setting the `message` field
+in`AppOpsManager.noteOp` to the system-identity hash code of the registered listener. There are
+convenience methods for that, e.g. `AppOpsManager.toReceiverId`. This `message` field is then
+delivered to the app inside the `AsyncNotedAppOp` parameter to `OnOpNotedCallback.onAsyncNoted`.
+
+While this case is not as elegant as the synchronous case, a properly set `message` can often be
+enough for the app to figure out where the data access comes from. Async data accesses are less
+common than synchronous data accesses but they come in more variations. E.g. registered listeners,
+pending-intents, manifest broadcast receivers, activity starts, etc... Hence there is no one perfect
+message format. This is why the message field is a free text string.
+
+It is very highly recommended for data providers to set appropriate `message` parameters for their
+`AppOpsManager.noteOp` calls for all times where there is async data access. If no `message`
+parameter is set, the system defaults to a stack trace of the data provider code which is often slow
+and not useful.
+
+Async data accesses also carry the attribution tag, but this can sometimes not be enough. Again, a
+properly set `message` parameter is the best choice.
+
+##### Data providers implemented in native code
+
+Some data providers (e.g. camera a microphone) are implemented using native code. As of now this is
+not properly hooked up to the Java logic. To make sure to always collect all data accesses all
+`AppOpsManager::noteOp` calls from native code trigger an [async data access](#async-data-accesses),
+no matter if the code is in a synchronous RPC or not.
+
+This is not ideal and should be improved.
+
+### Getting last data accesses via an API
To get the last accesses for an op or package an app can use `AppOpsManager.getPackagesForOps`.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bac5025..5222699 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1670,7 +1670,9 @@
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/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 301d188..9e1c505 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -214,4 +214,11 @@
* @param displayId id of the display where activity will rotate
*/
void onActivityRotation(int displayId);
+
+ /**
+ * Called when a task is moved to the back behind the home stack.
+ *
+ * @param taskInfo info about the task which moved
+ */
+ void onTaskMovedToBack(in ActivityManager.RunningTaskInfo taskInfo);
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 50853a3..c01b5a3 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1617,7 +1617,9 @@
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/Notification.java b/core/java/android/app/Notification.java
index f6a06f3..209286c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1198,6 +1198,14 @@
"android.pictureContentDescription";
/**
+ * {@link #extras} key: this is a boolean to indicate that the
+ * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state
+ * of a {@link BigPictureStyle} notification. This will replace a
+ * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
+ */
+ public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
+
+ /**
* {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
* notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
*/
@@ -5149,6 +5157,7 @@
// changes are entirely visual, and should otherwise be undetectable by apps.
@SuppressWarnings("AndroidFrameworkCompatChange")
private void calculateLargeIconDimens(boolean largeIconShown,
+ @NonNull StandardTemplateParams p,
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
@@ -5159,9 +5168,9 @@
final float viewHeightDp = resources.getDimension(
R.dimen.notification_right_icon_size) / density;
float viewWidthDp = viewHeightDp; // icons are 1:1 by default
- if (largeIconShown && (
- mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
- || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
+ if (largeIconShown && (p.mPromotePicture
+ || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
+ || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
if (drawable != null) {
int iconWidth = drawable.getIntrinsicWidth();
@@ -5187,7 +5196,7 @@
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
- calculateLargeIconDimens(showLargeIcon, result);
+ calculateLargeIconDimens(showLargeIcon, p, result);
if (showLargeIcon) {
contentView.setViewLayoutWidth(R.id.right_icon,
result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
@@ -6995,6 +7004,7 @@
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
+ private boolean mPromotePicture;
public BigPictureStyle() {
}
@@ -7011,7 +7021,8 @@
* Overrides ContentTitle in the big form of the template.
* This defaults to the value passed to setContentTitle().
*/
- public BigPictureStyle setBigContentTitle(CharSequence title) {
+ @NonNull
+ public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) {
internalSetBigContentTitle(safeCharSequence(title));
return this;
}
@@ -7019,7 +7030,8 @@
/**
* Set the first line of text after the detail section in the big form of the template.
*/
- public BigPictureStyle setSummaryText(CharSequence cs) {
+ @NonNull
+ public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
return this;
}
@@ -7044,22 +7056,36 @@
/**
* Provide the bitmap to be used as the payload for the BigPicture notification.
*/
- public BigPictureStyle bigPicture(Bitmap b) {
+ @NonNull
+ public BigPictureStyle bigPicture(@Nullable Bitmap b) {
mPicture = b;
return this;
}
/**
+ * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and
+ * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed
+ * state of this notification.
+ */
+ @NonNull
+ public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
+ mPromotePicture = show;
+ return this;
+ }
+
+ /**
* Override the large icon when the big notification is shown.
*/
- public BigPictureStyle bigLargeIcon(Bitmap b) {
+ @NonNull
+ public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null);
}
/**
* Override the large icon when the big notification is shown.
*/
- public BigPictureStyle bigLargeIcon(Icon icon) {
+ @NonNull
+ public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
mBigLargeIconSet = true;
mBigLargeIcon = icon;
return this;
@@ -7112,6 +7138,66 @@
/**
* @hide
*/
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ if (mPicture == null || !mPromotePicture) {
+ return super.makeContentView(increasedHeight);
+ }
+
+ Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
+ mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ // The legacy largeIcon might not allow us to clear the image, as it's taken in
+ // replacement if the other one is null. Because we're restoring these legacy icons
+ // for old listeners, this is in general non-null.
+ Bitmap largeIconLegacy = mBuilder.mN.largeIcon;
+ mBuilder.mN.largeIcon = null;
+
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .fillTextsFrom(mBuilder)
+ .promotePicture(true);
+ RemoteViews contentView = getStandardView(mBuilder.getBaseLayoutResource(),
+ p, null /* result */);
+
+ mBuilder.mN.mLargeIcon = oldLargeIcon;
+ mBuilder.mN.largeIcon = largeIconLegacy;
+
+ return contentView;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ if (mPicture == null || !mPromotePicture) {
+ return super.makeHeadsUpContentView(increasedHeight);
+ }
+
+ Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
+ mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ // The legacy largeIcon might not allow us to clear the image, as it's taken in
+ // replacement if the other one is null. Because we're restoring these legacy icons
+ // for old listeners, this is in general non-null.
+ Bitmap largeIconLegacy = mBuilder.mN.largeIcon;
+ mBuilder.mN.largeIcon = null;
+
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .fillTextsFrom(mBuilder)
+ .promotePicture(true);
+ RemoteViews contentView = getStandardView(mBuilder.getHeadsUpBaseLayoutResource(),
+ p, null /* result */);
+
+ mBuilder.mN.mLargeIcon = oldLargeIcon;
+ mBuilder.mN.largeIcon = largeIconLegacy;
+
+ return contentView;
+ }
+
+ /**
+ * @hide
+ */
public RemoteViews makeBigContentView() {
// Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
// This covers the following cases:
@@ -7168,6 +7254,7 @@
extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
mPictureContentDescription);
}
+ extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture);
extras.putParcelable(EXTRA_PICTURE, mPicture);
}
@@ -7188,6 +7275,7 @@
extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
}
+ mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE);
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
@@ -8962,22 +9050,9 @@
return remoteViews;
}
- // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
- // a use case that is not supported by the Compat Framework library. Workarounds to resolve
- // the change's state in NotificationManagerService were very complex. While it's possible
- // apps can detect the change, it's most likely that the changes will simply result in
- // visual regressions.
- @SuppressWarnings("AndroidFrameworkCompatChange")
private int getDecorationType() {
ContentResolver contentResolver = mBuilder.mContext.getContentResolver();
- if (mBuilder.mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
- || DevFlags.shouldBackportSNotifRules(contentResolver)) {
- return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver);
- } else {
- // For apps that don't target S, this decoration provides the closest behavior to R,
- // but doesn't fit with the design guidelines for S.
- return DevFlags.DECORATION_FULL_COMPATIBLE;
- }
+ return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver);
}
/**
@@ -11306,6 +11381,7 @@
boolean mHideTitle;
boolean mHideActions;
boolean mHideProgress;
+ boolean mPromotePicture;
CharSequence title;
CharSequence text;
CharSequence headerTextSecondary;
@@ -11321,6 +11397,7 @@
mHideTitle = false;
mHideActions = false;
mHideProgress = false;
+ mPromotePicture = false;
title = null;
text = null;
summaryText = null;
@@ -11360,6 +11437,11 @@
return this;
}
+ final StandardTemplateParams promotePicture(boolean promotePicture) {
+ this.mPromotePicture = promotePicture;
+ return this;
+ }
+
final StandardTemplateParams title(CharSequence title) {
this.title = title;
return this;
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 6d79e2d..60bfac5 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -42,8 +42,10 @@
# 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 602e9a3..269ccb1 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -40,7 +40,6 @@
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.ArraySet;
-import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.os.IResultReceiver;
@@ -108,7 +107,6 @@
* FLAG_ONE_SHOT, <b>both</b> FLAG_ONE_SHOT and FLAG_NO_CREATE need to be supplied.
*/
public final class PendingIntent implements Parcelable {
- private static final String TAG = "PendingIntent";
private final IIntentSender mTarget;
private IResultReceiver mCancelReceiver;
private IBinder mWhitelistToken;
@@ -354,7 +352,7 @@
if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
&& !flagImmutableSet && !flagMutableSet) {
- Log.e(TAG, msg);
+ throw new IllegalArgumentException(msg);
}
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 4e3d85c..7d2bc1a 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -35,6 +35,7 @@
import android.view.View;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -263,6 +264,28 @@
}
/**
+ * Simulate notification click for testing
+ *
+ * @hide
+ */
+ @TestApi
+ public void clickNotification(@Nullable String key, int rank, int count, boolean visible) {
+ clickNotificationInternal(key, rank, count, visible);
+ }
+
+ private void clickNotificationInternal(String key, int rank, int count, boolean visible) {
+ try {
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.onNotificationClick(key,
+ NotificationVisibility.obtain(key, rank, count, visible));
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Expand the notifications panel.
*
* @hide
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index c311d718..d1b544d 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -17,13 +17,13 @@
package android.app;
import android.app.ActivityManager.RunningTaskInfo;
-import android.window.TaskSnapshot;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
+import android.window.TaskSnapshot;
/**
* Classes interested in observing only a subset of changes using ITaskStackListener can extend
@@ -196,4 +196,8 @@
@Override
public void onActivityRotation(int displayId) {
}
+
+ @Override
+ public void onTaskMovedToBack(RunningTaskInfo taskInfo) {
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 54e1ac43..7e8fb91 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1686,6 +1686,20 @@
public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000;
/**
+ * A boolean extra for {@link #ACTION_SET_NEW_PARENT_PROFILE_PASSWORD} requesting that only
+ * device password requirement is enforced during the parent profile password enrolment flow.
+ * <p> Normally when enrolling password for the parent profile, both the device-wide password
+ * requirement (requirement set via {@link #getParentProfileInstance(ComponentName)} instance)
+ * and the profile password requirement are enforced, if the profile currently does not have a
+ * separate work challenge. By setting this to {@code true}, profile password requirement is
+ * explicitly disregarded.
+ *
+ * @see #isActivePasswordSufficientForDeviceRequirement()
+ */
+ public static final String EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY =
+ "android.app.extra.DEVICE_PASSWORD_REQUIREMENT_ONLY";
+
+ /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -1700,8 +1714,10 @@
/**
* Activity action: have the user enter a new password for the parent profile.
* If the intent is launched from within a managed profile, this will trigger
- * entering a new password for the parent of the profile. In all other cases
- * the behaviour is identical to {@link #ACTION_SET_NEW_PASSWORD}.
+ * entering a new password for the parent of the profile. The caller can optionally
+ * set {@link #EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY} to only enforce device-wide
+ * password requirement. In all other cases the behaviour is identical to
+ * {@link #ACTION_SET_NEW_PASSWORD}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD
@@ -3844,9 +3860,21 @@
* @hide
*/
public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ return getPasswordMinimumMetrics(userHandle, false);
+ }
+
+ /**
+ * Returns minimum PasswordMetrics that satisfies all admin policies.
+ * If requested, only consider device-wide admin policies and ignore policies set on the
+ * managed profile instance (as if the managed profile had separate work challenge).
+ *
+ * @hide
+ */
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle,
+ boolean deviceWideOnly) {
if (mService != null) {
try {
- return mService.getPasswordMinimumMetrics(userHandle);
+ return mService.getPasswordMinimumMetrics(userHandle, deviceWideOnly);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4125,6 +4153,7 @@
* @throws SecurityException if the calling application is not a profile owner of a managed
* profile, or if this API is not called on the parent DevicePolicyManager instance.
* @throws IllegalStateException if the user isn't unlocked
+ * @see #EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY
*/
public boolean isActivePasswordSufficientForDeviceRequirement() {
if (!mParentInstance) {
@@ -4250,12 +4279,25 @@
*/
@PasswordComplexity
public int getAggregatedPasswordComplexityForUser(int userId) {
+ return getAggregatedPasswordComplexityForUser(userId, false);
+ }
+
+ /**
+ * Returns the password complexity that applies to this user, aggregated from other users if
+ * necessary (for example, if the DPC has set password complexity requirements on the parent
+ * profile DPM instance of a managed profile user, they would apply to the primary user on the
+ * device). If {@code deviceWideOnly} is {@code true}, ignore policies set on the
+ * managed profile DPM instance (as if the managed profile had separate work challenge).
+ * @hide
+ */
+ @PasswordComplexity
+ public int getAggregatedPasswordComplexityForUser(int userId, boolean deviceWideOnly) {
if (mService == null) {
return PASSWORD_COMPLEXITY_NONE;
}
try {
- return mService.getAggregatedPasswordComplexityForUser(userId);
+ return mService.getAggregatedPasswordComplexityForUser(userId, deviceWideOnly);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8633,6 +8675,19 @@
* Called by a profile or device owner to set the permitted input methods services for this
* user. By default, the user can use any input method.
* <p>
+ * This method can be called on the {@link DevicePolicyManager} instance,
+ * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be
+ * a profile owner of an organization-owned device.
+ * <p>
+ * If called on the parent instance:
+ * <ul>
+ * <li>The permitted input methods will be applied on the personal profile</li>
+ * <li>Can only permit all input methods (calling this method with a {@code null} package
+ * list) or only permit system input methods (calling this method with an empty package
+ * list). This is to prevent the caller from learning which packages are installed on
+ * the personal side</li>
+ * </ul>
+ * <p>
* When zero or more packages have been added, input method that are not in the list and not
* part of the system can not be enabled by the user. This method will fail if it is called for
* a admin that is not for the foreground user or a profile of the foreground user. Any
@@ -8647,14 +8702,18 @@
* @param packageNames List of input method package names.
* @return {@code true} if the operation succeeded, or {@code false} if the list didn't
* contain every enabled non-system input method service.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a device, profile owner or if called on
+ * the parent profile and the {@code admin} is not a profile owner
+ * of an organization-owned managed profile.
+ * @throws IllegalArgumentException if called on the parent profile, the {@code admin} is a
+ * profile owner of an organization-owned managed profile and the
+ * list of permitted input method package names is not null or empty.
*/
public boolean setPermittedInputMethods(
@NonNull ComponentName admin, List<String> packageNames) {
- throwIfParentInstance("setPermittedInputMethods");
if (mService != null) {
try {
- return mService.setPermittedInputMethods(admin, packageNames);
+ return mService.setPermittedInputMethods(admin, packageNames, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8666,18 +8725,25 @@
/**
* Returns the list of permitted input methods set by this device or profile owner.
* <p>
+ * This method can be called on the {@link DevicePolicyManager} instance,
+ * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be
+ * a profile owner of an organization-owned managed profile. If called on the parent instance,
+ * then the returned list of permitted input methods are those which are applied on the
+ * personal profile.
+ * <p>
* An empty list means no input methods except system input methods are allowed. Null means all
* input methods are allowed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return List of input method package names.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a device, profile owner or if called on
+ * the parent profile and the {@code admin} is not a profile owner
+ * of an organization-owned managed profile.
*/
public @Nullable List<String> getPermittedInputMethods(@NonNull ComponentName admin) {
- throwIfParentInstance("getPermittedInputMethods");
if (mService != null) {
try {
- return mService.getPermittedInputMethods(admin);
+ return mService.getPermittedInputMethods(admin, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8687,6 +8753,11 @@
/**
* Called by the system to check if a specific input method is disabled by admin.
+ * <p>
+ * This method can be called on the {@link DevicePolicyManager} instance,
+ * returned by {@link #getParentProfileInstance(ComponentName)}. If called on the parent
+ * instance, this method will check whether the given input method is permitted on
+ * the personal profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageName Input method package name that needs to be checked.
@@ -8699,7 +8770,8 @@
@NonNull String packageName, int userHandle) {
if (mService != null) {
try {
- return mService.isInputMethodPermittedByAdmin(admin, packageName, userHandle);
+ return mService.isInputMethodPermittedByAdmin(admin, packageName, userHandle,
+ mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10786,6 +10858,8 @@
* <li>{@link #setCameraDisabled}</li>
* <li>{@link #getCameraDisabled}</li>
* <li>{@link #setAccountManagementDisabled(ComponentName, String, boolean)}</li>
+ * <li>{@link #setPermittedInputMethods}</li>
+ * <li>{@link #getPermittedInputMethods}</li>
* </ul>
*
* <p>The following methods can be called by the profile owner of a managed profile
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e855a1c..8f84bfe 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -73,7 +73,7 @@
void setPasswordMinimumNonLetter(in ComponentName who, int length, boolean parent);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle, boolean parent);
- PasswordMetrics getPasswordMinimumMetrics(int userHandle);
+ PasswordMetrics getPasswordMinimumMetrics(int userHandle, boolean deviceWideOnly);
void setPasswordHistoryLength(in ComponentName who, int length, boolean parent);
int getPasswordHistoryLength(in ComponentName who, int userHandle, boolean parent);
@@ -90,7 +90,7 @@
int getPasswordComplexity(boolean parent);
void setRequiredPasswordComplexity(int passwordComplexity, boolean parent);
int getRequiredPasswordComplexity(boolean parent);
- int getAggregatedPasswordComplexityForUser(int userId);
+ int getAggregatedPasswordComplexityForUser(int userId, boolean deviceWideOnly);
boolean isUsingUnifiedPassword(in ComponentName admin);
int getCurrentFailedPasswordAttempts(int userHandle, boolean parent);
int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent);
@@ -232,10 +232,10 @@
List getPermittedAccessibilityServicesForUser(int userId);
boolean isAccessibilityServicePermittedByAdmin(in ComponentName admin, String packageName, int userId);
- boolean setPermittedInputMethods(in ComponentName admin,in List packageList);
- List getPermittedInputMethods(in ComponentName admin);
+ boolean setPermittedInputMethods(in ComponentName admin,in List packageList, boolean parent);
+ List getPermittedInputMethods(in ComponentName admin, boolean parent);
List getPermittedInputMethodsForCurrentUser();
- boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
+ boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId, boolean parent);
boolean setPermittedCrossProfileNotificationListeners(in ComponentName admin, in List<String> packageList);
List<String> getPermittedCrossProfileNotificationListeners(in ComponentName admin);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 0fcf44d..3788257 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -174,8 +174,10 @@
@NonNull
private final Object mListenersLock = new Object();
- @NonNull
- private final RoleControllerManager mRoleControllerManager;
+ @GuardedBy("mRoleControllerManagerLock")
+ @Nullable
+ private RoleControllerManager mRoleControllerManager;
+ private final Object mRoleControllerManagerLock = new Object();
/**
* @hide
@@ -184,7 +186,6 @@
mContext = context;
mService = IRoleManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
Context.ROLE_SERVICE));
- mRoleControllerManager = new RoleControllerManager(context);
}
/**
@@ -693,7 +694,7 @@
@TestApi
public void isRoleVisible(@NonNull String roleName,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
- mRoleControllerManager.isRoleVisible(roleName, executor, callback);
+ getRoleControllerManager().isRoleVisible(roleName, executor, callback);
}
/**
@@ -714,10 +715,20 @@
@TestApi
public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
- mRoleControllerManager.isApplicationVisibleForRole(roleName, packageName, executor,
+ getRoleControllerManager().isApplicationVisibleForRole(roleName, packageName, executor,
callback);
}
+ @NonNull
+ private RoleControllerManager getRoleControllerManager() {
+ synchronized (mRoleControllerManagerLock) {
+ if (mRoleControllerManager == null) {
+ mRoleControllerManager = new RoleControllerManager(mContext);
+ }
+ return mRoleControllerManager;
+ }
+ }
+
private static class OnRoleHoldersChangedListenerDelegate
extends IOnRoleHoldersChangedListener.Stub {
diff --git a/core/java/android/app/sync-data-access.png b/core/java/android/app/sync-data-access.png
new file mode 100644
index 0000000..49603d5
--- /dev/null
+++ b/core/java/android/app/sync-data-access.png
Binary files differ
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index c4d9867..0188637 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1008,7 +1008,9 @@
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/Intent.java b/core/java/android/content/Intent.java
index 7843d97..1752b48 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6681,6 +6681,25 @@
| 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 @@
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 @@
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 @@
/** @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 @@
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 @@
mContentUserHint = UserHandle.USER_CURRENT;
}
}
+
+ if (fromProtectedComponent) {
+ mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
+ }
}
/** @hide */
diff --git a/core/java/android/content/pm/AppSearchPerson.java b/core/java/android/content/pm/AppSearchPerson.java
index 045c55f..d70ac91 100644
--- a/core/java/android/content/pm/AppSearchPerson.java
+++ b/core/java/android/content/pm/AppSearchPerson.java
@@ -47,32 +47,24 @@
}
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 14b8df8..85549d8 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -73,131 +73,100 @@
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/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 03d4d5e..747f8dc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3532,6 +3532,17 @@
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration";
+ /**
+ * Feature for {@link android.view.WindowManager.LayoutParams.backgroundBlurRedius} and
+ * {@link android.graphics.drawable.BackgroundBlurDrawable}: the device supports cross-layer
+ * blurring.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index a54e88f..dbcd79d 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -41,4 +41,7 @@
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 e165ad6..f4f9e17 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -22,6 +22,7 @@
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 @@
@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 @@
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/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 582570e..d932865 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -89,6 +89,8 @@
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 @@
*/
@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 @@
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 @@
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 @@
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/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 095b8e9..1bd284d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -30,4 +30,6 @@
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/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index b09eda4..7858499 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -454,6 +454,33 @@
@Retention(RetentionPolicy.SOURCE)
public @interface SystemAudioModeMuting {}
+ // -- Whether the HDMI CEC volume control is enabled or disabled.
+ /**
+ * HDMI CEC enabled.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
+ * @hide
+ */
+ public static final int VOLUME_CONTROL_ENABLED = 1;
+ /**
+ * HDMI CEC disabled.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
+ * @hide
+ */
+ public static final int VOLUME_CONTROL_DISABLED = 0;
+ /**
+ * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
+ * @hide
+ */
+ @IntDef({
+ VOLUME_CONTROL_ENABLED,
+ VOLUME_CONTROL_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VolumeControl {}
+
+
// -- Settings available in the CEC Configuration.
/**
* Name of a setting deciding whether the CEC is enabled.
@@ -492,6 +519,43 @@
@SystemApi
public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING =
"system_audio_mode_muting";
+
+ /**
+ * Controls whether volume control commands via HDMI CEC are enabled.
+ *
+ * <p>Effects on different device types:
+ * <table>
+ * <tr><th>HDMI CEC device type</th><th>0: disabled</th><th>1: enabled</th></tr>
+ * <tr>
+ * <td>TV (type: 0)</td>
+ * <td>Per CEC specification.</td>
+ * <td>TV changes system volume. TV no longer reacts to incoming volume changes
+ * via {@code <User Control Pressed>}. TV no longer handles {@code <Report Audio
+ * Status>}.</td>
+ * </tr>
+ * <tr>
+ * <td>Playback device (type: 4)</td>
+ * <td>Device sends volume commands to TV/Audio system via {@code <User Control
+ * Pressed>}</td>
+ * <td>Device does not send volume commands via {@code <User Control Pressed>}.</td>
+ * </tr>
+ * <tr>
+ * <td>Audio device (type: 5)</td>
+ * <td>Full "System Audio Control" capabilities.</td>
+ * <td>Audio device no longer reacts to incoming {@code <User Control Pressed>}
+ * volume commands. Audio device no longer reports volume changes via {@code
+ * <Report Audio Status>}.</td>
+ * </tr>
+ * </table>
+ *
+ * <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
+ *
+ * @hide
+ * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(int)
+ */
+ public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE =
+ "volume_control_enabled";
+
/**
* @hide
*/
@@ -501,6 +565,7 @@
CEC_SETTING_NAME_POWER_CONTROL_MODE,
CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+ CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
})
public @interface CecSettingName {}
@@ -913,14 +978,16 @@
*
* <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
*
- * @param isHdmiCecVolumeControlEnabled target state of HDMI CEC volume control.
- * @see Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED
+ * @param hdmiCecVolumeControlEnabled target state of HDMI CEC volume control.
+ * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
* @hide
*/
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
- public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
+ public void setHdmiCecVolumeControlEnabled(
+ @VolumeControl int hdmiCecVolumeControlEnabled) {
try {
- mService.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
+ mService.setCecSettingIntValue(CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ hdmiCecVolumeControlEnabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -931,9 +998,10 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
- public boolean isHdmiCecVolumeControlEnabled() {
+ @VolumeControl
+ public int getHdmiCecVolumeControlEnabled() {
try {
- return mService.isHdmiCecVolumeControlEnabled();
+ return mService.getCecSettingIntValue(CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1042,7 +1110,8 @@
*
* Note: Value of isCecAvailable is only valid when isCecEnabled is true.
**/
- void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+ void onStatusChange(@HdmiControlManager.HdmiCecControl int isCecEnabled,
+ boolean isCecAvailable);
}
private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
@@ -1056,10 +1125,10 @@
/**
* Called when the HDMI Control (CEC) volume control feature is enabled/disabled.
*
- * @param enabled status of HDMI CEC volume control feature
- * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()}
+ * @param hdmiCecVolumeControl status of HDMI CEC volume control feature
+ * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(int)} ()}
**/
- void onHdmiCecVolumeControlFeature(boolean enabled);
+ void onHdmiCecVolumeControlFeature(@VolumeControl int hdmiCecVolumeControl);
}
private final ArrayMap<HdmiCecVolumeControlFeatureListener,
@@ -1283,7 +1352,7 @@
Executor executor, final HdmiControlStatusChangeListener listener) {
return new IHdmiControlStatusChangeListener.Stub() {
@Override
- public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
+ public void onStatusChange(@HdmiCecControl int isCecEnabled, boolean isCecAvailable) {
final long token = Binder.clearCallingIdentity();
try {
executor.execute(() -> listener.onStatusChange(isCecEnabled, isCecAvailable));
@@ -1360,7 +1429,7 @@
Executor executor, final HdmiCecVolumeControlFeatureListener listener) {
return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() {
@Override
- public void onHdmiCecVolumeControlFeature(boolean enabled) {
+ public void onHdmiCecVolumeControlFeature(int enabled) {
final long token = Binder.clearCallingIdentity();
try {
executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled));
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 89a7afa8..9a9e945 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -273,17 +273,6 @@
}
@Override
- public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
- HdmiControlServiceWrapper.this.setHdmiCecVolumeControlEnabled(
- isHdmiCecVolumeControlEnabled);
- }
-
- @Override
- public boolean isHdmiCecVolumeControlEnabled() {
- return HdmiControlServiceWrapper.this.isHdmiCecVolumeControlEnabled();
- }
-
- @Override
public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) {
HdmiControlServiceWrapper.this.reportAudioStatus(deviceType, volume, maxVolume, isMute);
}
diff --git a/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl
index 873438b..f7c5887 100644
--- a/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl
@@ -26,7 +26,7 @@
* Called when the HDMI Control (CEC) volume control feature is enabled/disabled.
*
* @param enabled status of HDMI CEC volume control feature
- * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()}
+ * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(int)} ()}
**/
- void onHdmiCecVolumeControlFeature(boolean enabled);
+ void onHdmiCecVolumeControlFeature(int hdmiCecVolumeControl);
}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index d7329e0..7f0e53e 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -86,8 +86,6 @@
void sendMhlVendorCommand(int portId, int offset, int length, in byte[] data);
void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
void setStandbyMode(boolean isStandbyModeOn);
- void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled);
- boolean isHdmiCecVolumeControlEnabled();
void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
void setSystemAudioModeOnForAudioOnlySource();
void addCecSettingChangeListener(String name, IHdmiCecSettingChangeListener listener);
diff --git a/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
index 889d3fe..d61ab6a 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
@@ -28,11 +28,11 @@
* Called when HDMI Control (CEC) is enabled/disabled.
*
* @param isCecEnabled status of HDMI Control
- * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+ * {@link android.hardware.hdmi.HdmiControlManager#CEC_SETTING_NAME_HDMI_CEC_ENABLED}: {@link android.hardware.hdmi.HdmiControlManager#HDMI_CEC_CONTROL_ENABLED} if enabled.
* @param isCecAvailable status of CEC support of the connected display (the TV).
* {@code true} if supported.
*
- * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+ * Note: Value of isCecAvailable is only valid when isCecEnabled is {@link android.hardware.hdmi.HdmiControlManager#HDMI_CEC_CONTROL_ENABLED}.
**/
- void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+ void onStatusChange(int isCecEnabled, boolean isCecAvailable);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index df9a7c2..44a2e97 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -410,6 +410,11 @@
private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
+ /**
+ * Timeout after which hidden IME surface will be removed from memory
+ */
+ private static final long TIMEOUT_SURFACE_REMOVAL_MILLIS = 5000;
+
InputMethodManager mImm;
private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
@@ -506,6 +511,8 @@
private boolean mAutomotiveHideNavBarForKeyboard;
private boolean mIsAutomotive;
+ private Handler mHandler;
+ private boolean mImeSurfaceScheduledForRemoval;
/**
* An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
@@ -903,11 +910,31 @@
requestHideSelf(0);
}
+ private void scheduleImeSurfaceRemoval() {
+ if (mShowInputRequested || mWindowVisible || mWindow == null
+ || mImeSurfaceScheduledForRemoval) {
+ return;
+ }
+ if (mHandler == null) {
+ mHandler = new Handler(getMainLooper());
+ }
+ mImeSurfaceScheduledForRemoval = true;
+ mHandler.postDelayed(() -> removeImeSurface(), TIMEOUT_SURFACE_REMOVAL_MILLIS);
+ }
+
private void removeImeSurface() {
- if (!mShowInputRequested && !mWindowVisible) {
- // hiding a window removes its surface.
+ // hiding a window removes its surface.
+ if (mWindow != null) {
mWindow.hide();
}
+ mImeSurfaceScheduledForRemoval = false;
+ }
+
+ private void cancelImeSurfaceRemoval() {
+ if (mHandler != null && mImeSurfaceScheduledForRemoval) {
+ mHandler.removeCallbacksAndMessages(null /* token */);
+ mImeSurfaceScheduledForRemoval = false;
+ }
}
private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
@@ -1043,7 +1070,7 @@
* @hide
*/
public final void removeImeSurface() {
- InputMethodService.this.removeImeSurface();
+ InputMethodService.this.scheduleImeSurfaceRemoval();
}
}
@@ -2271,6 +2298,9 @@
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", this,
null /* icProto */);
+ if (setVisible) {
+ cancelImeSurfaceRemoval();
+ }
mPrivOps.applyImeVisibility(setVisible
? mCurShowInputToken : mCurHideInputToken, setVisible);
}
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index ab12cdd..3d79f28 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -39,7 +39,11 @@
* An event logged when there is a change or event that requires updating the
* the APF program in place with a new APF program.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class ApfProgramEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index fcafb7e..a32d3a6 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -27,7 +27,11 @@
/**
* An event logged for an interface with APF capabilities when its IpClient state machine exits.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class ApfStats implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 8de427d..e175d58 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -28,7 +28,11 @@
/**
* An event recorded when a DhcpClient state machine transitions to a new state.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class DhcpClientEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index de3129d..7dd0696 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -27,7 +27,11 @@
/**
* Event class used to record error events when parsing DHCP response packets.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class DhcpErrorEvent implements IpConnectivityLog.Event {
public static final int L2_ERROR = 1;
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index bb91f89..5cadb45 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -35,7 +35,11 @@
/**
* Class for logging IpConnectvity events with IpConnectivityMetrics
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public class IpConnectivityLog {
private static final String TAG = IpConnectivityLog.class.getSimpleName();
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 4f7f326..3abcc05 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -33,7 +33,11 @@
* An event recorded by IpClient when IP provisioning completes for a network or
* when a network disconnects.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class IpManagerEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d5003ba..0b65bbd 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -29,7 +29,11 @@
* An event recorded when IpReachabilityMonitor sends a neighbor probe or receives
* a neighbor probe result.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class IpReachabilityEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 8c28f7a..47eeeff 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -31,7 +31,11 @@
/**
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class NetworkEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index b54874f..05a47e5 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -25,7 +25,11 @@
/**
* An event logged when the APF packet socket receives an RA packet.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class RaEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 7f4e4a7..8118fe0 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -32,7 +32,11 @@
/**
* An event recorded by NetworkMonitor when sending a probe for finding captive portals.
* {@hide}
+ * @deprecated The event may not be sent in Android S and above. The events
+ * are logged by a single caller in the system using signature permissions
+ * and that caller is migrating to statsd.
*/
+@Deprecated
@SystemApi
public final class ValidationProbeEvent implements IpConnectivityLog.Event {
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 0b2cfdd..bc3d5c4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -72,4 +72,7 @@
boolean deviceSupportsNfcSecure();
boolean setNfcSecure(boolean enable);
+ boolean setAlwaysOn(boolean value);
+ boolean isAlwaysOnEnabled();
+ boolean isAlwaysOnSupported();
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index a17a5370..e85eb93 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -350,6 +350,22 @@
"android.nfc.extra.HANDOVER_TRANSFER_STATUS";
/** @hide */
+ public static final String ACTION_ALWAYS_ON_STATE_CHANGED =
+ "android.nfc.action.ALWAYS_ON_STATE_CHANGED";
+
+ /**
+ * Used as an int extra field in {@link #ACTION_ALWAYS_ON_STATE_CHANGED}
+ * intents to request the current power state. Possible values are:
+ * {@link #STATE_OFF},
+ * {@link #STATE_TURNING_ON},
+ * {@link #STATE_ON},
+ * {@link #STATE_TURNING_OFF},
+ * @hide
+ */
+ public static final String EXTRA_ALWAYS_ON_STATE =
+ "android.nfc.extra.ALWAYS_ON_STATE";
+
+ /** @hide */
public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
/** @hide */
public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
@@ -358,6 +374,14 @@
public static final String EXTRA_HANDOVER_TRANSFER_URI =
"android.nfc.extra.HANDOVER_TRANSFER_URI";
+ /**
+ * Broadcast Action: Notify possible NFC transaction blocked because device is locked.
+ * <p>An external NFC field detected when device locked and SecureNfc enabled.
+ * @hide
+ */
+ public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC =
+ "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
@@ -2211,4 +2235,106 @@
return mContext.getApplicationInfo().targetSdkVersion;
}
}
+
+ /**
+ * Sets NFC controller always on feature.
+ * <p>This API is for the NFCC internal state management. It allows to discriminate
+ * the controller function from the NFC function by keeping the NFC Controller on without
+ * any NFC RF enabled if necessary.
+ * <p>This call is asynchronous. Listen for {@link #ACTION_ALWAYS_ON_STATE_CHANGED}
+ * broadcasts to find out when the operation is complete.
+ * <p>If this returns true, then either NFCC is already on, or
+ * a {@link #ACTION_ALWAYS_ON_STATE_CHANGED} broadcast will be sent to indicate
+ * a state transition.
+ * If this returns false, then there is some problem that prevents an attempt to turn NFCC on.
+ * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
+ * disabled), if false the NFCC will follow completely the Nfc adapter state.
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @return void
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean setAlwaysOn(boolean value) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.setAlwaysOn(value);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.setAlwaysOn(value);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC controller always on feature is enabled.
+ *
+ * @return True if NFC controller always on is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @hide
+ */
+
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isAlwaysOnEnabled() {
+ try {
+ return sService.isAlwaysOnEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isAlwaysOnEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the device supports NFC controller always on functionality.
+ *
+ * @return True if device supports NFC controller always on, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @hide
+ */
+
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isAlwaysOnSupported() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isAlwaysOnSupported();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isAlwaysOnSupported();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
}
diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java
index fdccaae..2256365 100644
--- a/core/java/android/nfc/tech/Ndef.java
+++ b/core/java/android/nfc/tech/Ndef.java
@@ -112,7 +112,7 @@
public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
/** NFC Forum Tag Type 2 */
public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
- /** NFC Forum Tag Type 4 */
+ /** NFC Forum Tag Type 3 */
public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
/** NFC Forum Tag Type 4 */
public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index b13be9f..1189fdb 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -52,6 +52,7 @@
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 @@
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 @@
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 @@
* <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 @@
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 @@
}
/**
+ * 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 @@
}
/** @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 @@
}
}
+ /** @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/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0330500..30afe38 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -27,6 +28,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -38,6 +41,14 @@
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
+ private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
+ private static final int VIBRATOR_PRESENT_YES = 1;
+ private static final int VIBRATOR_PRESENT_NO = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
+ private @interface VibratorPresent {}
+
private final IVibratorService mService;
private final IVibratorManagerService mManagerService;
private final Object mLock = new Object();
@@ -45,6 +56,9 @@
private final Context mContext;
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
+ @GuardedBy("mLock")
+ @VibratorPresent
+ private int mVibratorPresent;
@GuardedBy("mDelegates")
private final ArrayMap<OnVibratorStateChangedListener,
@@ -69,15 +83,18 @@
@Override
public boolean hasVibrator() {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ try {
+ synchronized (mLock) {
+ if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
+ mVibratorPresent =
+ mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
+ }
+ return mVibratorPresent == VIBRATOR_PRESENT_YES;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to query vibrator presence", e);
return false;
}
- try {
- return mService.hasVibrator();
- } catch (RemoteException e) {
- }
- return false;
}
/**
diff --git a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
new file mode 100644
index 0000000..891fb59
--- /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/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0bdd574..33e33ee 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
@@ -189,7 +190,7 @@
* This method is used by Telephony to store pictures selected by the user or sent from the
* remote party as part of a voice call with call composer. The {@link Uri} supplied in the
* callback can be used to retrieve the image via {@link ContentResolver#openFile} or stored in
- * the {@link Calls} table in the TODO: link column name.
+ * the {@link Calls} table in the {@link Calls#COMPOSER_PHOTO_URI} column.
*
* The caller is responsible for closing the {@link InputStream} after the callback indicating
* success or failure.
@@ -388,6 +389,320 @@
}
/**
+ * Used as an argument to {@link Calls#addCall(Context, AddCallParams)}.
+ *
+ * Contains details to log about a call.
+ * @hide
+ */
+ public static class AddCallParams {
+
+ /**
+ * Builder for the add-call parameters.
+ */
+ public static final class AddCallParametersBuilder {
+ private CallerInfo mCallerInfo;
+ private String mNumber;
+ private String mPostDialDigits;
+ private String mViaNumber;
+ private int mPresentation = TelecomManager.PRESENTATION_UNKNOWN;
+ private int mCallType = Calls.INCOMING_TYPE;
+ private int mFeatures;
+ private PhoneAccountHandle mAccountHandle;
+ private long mStart;
+ private int mDuration;
+ private Long mDataUsage = Long.MIN_VALUE;
+ private boolean mAddForAllUsers;
+ private UserHandle mUserToBeInsertedTo;
+ private boolean mIsRead;
+ private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
+ private CharSequence mCallScreeningAppName;
+ private String mCallScreeningComponentName;
+ private long mMissedReason = Calls.MISSED_REASON_NOT_MISSED;
+ private int mPriority = Calls.PRIORITY_NORMAL;
+ private String mSubject;
+ private double mLatitude = Double.NaN;
+ private double mLongitude = Double.NaN;
+ private Uri mPictureUri;
+
+ /**
+ * @param callerInfo the CallerInfo object to get the target contact from.
+ */
+ public @NonNull AddCallParametersBuilder setCallerInfo(
+ @NonNull CallerInfo callerInfo) {
+ mCallerInfo = callerInfo;
+ return this;
+ }
+
+ /**
+ * @param number the phone number to be added to the calls db
+ */
+ public @NonNull AddCallParametersBuilder setNumber(@NonNull String number) {
+ mNumber = number;
+ return this;
+ }
+
+ /**
+ * @param postDialDigits the post-dial digits that were dialed after the number,
+ * if it was outgoing. Otherwise it is ''.
+ */
+ public @NonNull AddCallParametersBuilder setPostDialDigits(
+ @NonNull String postDialDigits) {
+ mPostDialDigits = postDialDigits;
+ return this;
+ }
+
+ /**
+ * @param viaNumber the secondary number that the incoming call received with. If the
+ * call was received with the SIM assigned number, then this field must be ''.
+ */
+ public @NonNull AddCallParametersBuilder setViaNumber(@NonNull String viaNumber) {
+ mViaNumber = viaNumber;
+ return this;
+ }
+
+ /**
+ * @param presentation enum value from TelecomManager.PRESENTATION_xxx, which
+ * is set by the network and denotes the number presenting rules for
+ * "allowed", "payphone", "restricted" or "unknown"
+ */
+ public @NonNull AddCallParametersBuilder setPresentation(int presentation) {
+ mPresentation = presentation;
+ return this;
+ }
+
+ /**
+ * @param callType enumerated values for "incoming", "outgoing", or "missed"
+ */
+ public @NonNull AddCallParametersBuilder setCallType(int callType) {
+ mCallType = callType;
+ return this;
+ }
+
+ /**
+ * @param features features of the call (e.g. Video).
+ */
+ public @NonNull AddCallParametersBuilder setFeatures(int features) {
+ mFeatures = features;
+ return this;
+ }
+
+ /**
+ * @param accountHandle The accountHandle object identifying the provider of the call
+ */
+ public @NonNull AddCallParametersBuilder setAccountHandle(
+ @NonNull PhoneAccountHandle accountHandle) {
+ mAccountHandle = accountHandle;
+ return this;
+ }
+
+ /**
+ * @param start time stamp for the call in milliseconds
+ */
+ public @NonNull AddCallParametersBuilder setStart(long start) {
+ mStart = start;
+ return this;
+ }
+
+ /**
+ * @param duration call duration in seconds
+ */
+ public @NonNull AddCallParametersBuilder setDuration(int duration) {
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * @param dataUsage data usage for the call in bytes or
+ * {@link Long#MIN_VALUE} if data usage was not tracked
+ * for the call.
+ */
+ public @NonNull AddCallParametersBuilder setDataUsage(long dataUsage) {
+ mDataUsage = dataUsage;
+ return this;
+ }
+
+ /**
+ * @param addForAllUsers If true, the call is added to the call log of all currently
+ * running users. The caller must have the MANAGE_USERS permission if this is
+ * true.
+ */
+ public @NonNull AddCallParametersBuilder setAddForAllUsers(
+ boolean addForAllUsers) {
+ mAddForAllUsers = addForAllUsers;
+ return this;
+ }
+
+ /**
+ * @param userToBeInsertedTo {@link UserHandle} of user that the call is going to be
+ * inserted to. null if it is inserted to the current user.
+ * The value is ignored if {@link #setAddForAllUsers} is
+ * called with {@code true}.
+ */
+ @SuppressLint("UserHandleName")
+ public @NonNull AddCallParametersBuilder setUserToBeInsertedTo(
+ @NonNull UserHandle userToBeInsertedTo) {
+ mUserToBeInsertedTo = userToBeInsertedTo;
+ return this;
+ }
+
+ /**
+ * @param isRead Flag to show if the missed call log has been read by the user or not.
+ * Used for call log restore of missed calls.
+ */
+ public @NonNull AddCallParametersBuilder setIsRead(boolean isRead) {
+ mIsRead = isRead;
+ return this;
+ }
+
+ /**
+ * @param callBlockReason The reason why the call is blocked.
+ */
+ public @NonNull AddCallParametersBuilder setCallBlockReason(int callBlockReason) {
+ mCallBlockReason = callBlockReason;
+ return this;
+ }
+
+ /**
+ * @param callScreeningAppName The call screening application name which block the call.
+ */
+ public @NonNull AddCallParametersBuilder setCallScreeningAppName(
+ @NonNull CharSequence callScreeningAppName) {
+ mCallScreeningAppName = callScreeningAppName;
+ return this;
+ }
+
+ /**
+ * @param callScreeningComponentName The call screening component name which blocked
+ * the call.
+ */
+ public @NonNull AddCallParametersBuilder setCallScreeningComponentName(
+ @NonNull String callScreeningComponentName) {
+ mCallScreeningComponentName = callScreeningComponentName;
+ return this;
+ }
+
+ /**
+ * @param missedReason The encoded missed information of the call.
+ */
+ public @NonNull AddCallParametersBuilder setMissedReason(long missedReason) {
+ mMissedReason = missedReason;
+ return this;
+ }
+
+ /**
+ * @param priority The priority of the call, either {@link Calls#PRIORITY_NORMAL}
+ * or {@link Calls#PRIORITY_URGENT} as sent via call composer
+ */
+ public @NonNull AddCallParametersBuilder setPriority(int priority) {
+ mPriority = priority;
+ return this;
+ }
+
+ /**
+ * @param subject The subject as sent via call composer.
+ */
+ public @NonNull AddCallParametersBuilder setSubject(@NonNull String subject) {
+ mSubject = subject;
+ return this;
+ }
+
+ /**
+ * @param latitude Latitude of the location sent via call composer.
+ */
+ public @NonNull AddCallParametersBuilder setLatitude(double latitude) {
+ mLatitude = latitude;
+ return this;
+ }
+
+ /**
+ * @param longitude Longitude of the location sent via call composer.
+ */
+ public @NonNull AddCallParametersBuilder setLongitude(double longitude) {
+ mLongitude = longitude;
+ return this;
+ }
+
+ /**
+ * @param pictureUri {@link Uri} returned from {@link #storeCallComposerPictureAsUser}.
+ * Associates that stored picture with this call in the log.
+ */
+ public @NonNull AddCallParametersBuilder setPictureUri(@NonNull Uri pictureUri) {
+ mPictureUri = pictureUri;
+ return this;
+ }
+
+ /**
+ * Builds the object
+ */
+ public @NonNull AddCallParams build() {
+ return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
+ mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
+ mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead, mCallBlockReason,
+ mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
+ mPriority, mSubject, mLatitude, mLongitude, mPictureUri);
+ }
+ }
+
+ private CallerInfo mCallerInfo;
+ private String mNumber;
+ private String mPostDialDigits;
+ private String mViaNumber;
+ private int mPresentation;
+ private int mCallType;
+ private int mFeatures;
+ private PhoneAccountHandle mAccountHandle;
+ private long mStart;
+ private int mDuration;
+ private long mDataUsage;
+ private boolean mAddForAllUsers;
+ private UserHandle mUserToBeInsertedTo;
+ private boolean mIsRead;
+ private int mCallBlockReason;
+ private CharSequence mCallScreeningAppName;
+ private String mCallScreeningComponentName;
+ private long mMissedReason;
+ private int mPriority;
+ private String mSubject;
+ private double mLatitude = Double.NaN;
+ private double mLongitude = Double.NaN;
+ private Uri mPictureUri;
+
+ private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
+ String viaNumber, int presentation, int callType, int features,
+ PhoneAccountHandle accountHandle, long start, int duration, long dataUsage,
+ boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead,
+ int callBlockReason,
+ CharSequence callScreeningAppName, String callScreeningComponentName,
+ long missedReason,
+ int priority, String subject, double latitude, double longitude, Uri pictureUri) {
+ mCallerInfo = callerInfo;
+ mNumber = number;
+ mPostDialDigits = postDialDigits;
+ mViaNumber = viaNumber;
+ mPresentation = presentation;
+ mCallType = callType;
+ mFeatures = features;
+ mAccountHandle = accountHandle;
+ mStart = start;
+ mDuration = duration;
+ mDataUsage = dataUsage;
+ mAddForAllUsers = addForAllUsers;
+ mUserToBeInsertedTo = userToBeInsertedTo;
+ mIsRead = isRead;
+ mCallBlockReason = callBlockReason;
+ mCallScreeningAppName = callScreeningAppName;
+ mCallScreeningComponentName = callScreeningComponentName;
+ mMissedReason = missedReason;
+ mPriority = priority;
+ mSubject = subject;
+ mLatitude = latitude;
+ mLongitude = longitude;
+ mPictureUri = pictureUri;
+ }
+
+ }
+
+ /**
* Contains the recent calls.
*/
public static class Calls implements BaseColumns {
@@ -1066,6 +1381,83 @@
public static final String MISSED_REASON = "missed_reason";
/**
+ * The subject of the call, as delivered via call composer.
+ *
+ * For outgoing calls, contains the subject set by the local user. For incoming calls,
+ * contains the subject set by the remote caller. May be null if no subject was set.
+ * <p>Type: TEXT</p>
+ */
+ public static final String SUBJECT = "subject";
+
+ /**
+ * Used as a value in the {@link #PRIORITY} column.
+ *
+ * Indicates that the call is of normal priority. This is also the default value for calls
+ * that did not include call composer elements.
+ */
+ public static final int PRIORITY_NORMAL = 0;
+
+ /**
+ * Used as a value in the {@link #PRIORITY} column.
+ *
+ * Indicates that the call is of urgent priority.
+ */
+ public static final int PRIORITY_URGENT = 1;
+
+ /**
+ * The priority of the call, as delivered via call composer.
+ *
+ * For outgoing calls, contains the priority set by the local user. For incoming calls,
+ * contains the priority set by the remote caller. If no priority was set or the call
+ * did not include call composer elements, defaults to {@link #PRIORITY_NORMAL}.
+ * Valid values are {@link #PRIORITY_NORMAL} and {@link #PRIORITY_URGENT}.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String PRIORITY = "priority";
+
+ /**
+ * A reference to the picture that was sent via call composer.
+ *
+ * The string contained in this field should be converted to an {@link Uri} via
+ * {@link Uri#parse(String)}, then passed to {@link ContentResolver#openFileDescriptor}
+ * in order to obtain a file descriptor to access the picture data.
+ *
+ * The user may choose to delete the picture associated with a call independently of the
+ * call log entry, in which case {@link ContentResolver#openFileDescriptor} may throw a
+ * {@link FileNotFoundException}.
+ *
+ * Note that pictures sent or received via call composer will not be included in any
+ * backups of the call log.
+ *
+ * <p>Type: TEXT</p>
+ */
+ public static final String COMPOSER_PHOTO_URI = "composer_photo_uri";
+
+ /**
+ * A reference to the location that was sent via call composer.
+ *
+ * This column contains the content URI of the corresponding entry in {@link Locations}
+ * table, which contains the actual location data. The
+ * {@link Manifest.permission#ACCESS_FINE_LOCATION} permission is required to access that
+ * table.
+ *
+ * If your app has the appropriate permissions, the location data may be obtained by
+ * converting the value of this column to an {@link Uri} via {@link Uri#parse}, then passing
+ * the result to {@link ContentResolver#query}.
+ *
+ * The user may choose to delete the location associated with a call independently of the
+ * call log entry, in which case the {@link Cursor} returned from
+ * {@link ContentResolver#query} will either be {@code null} or empty, with
+ * {@link Cursor#getCount()} returning {@code 0}.
+ *
+ * This column will not be populated when a call is received while the device is locked, and
+ * it will not be part of any backups.
+ *
+ * <p>Type: TEXT</p>
+ */
+ public static final String LOCATION = "location";
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
@@ -1139,6 +1531,7 @@
null /* callScreeningComponentName */, missedReason);
}
+
/**
* Adds a call to the call log.
*
@@ -1184,19 +1577,52 @@
Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
boolean isRead, int callBlockReason, CharSequence callScreeningAppName,
String callScreeningComponentName, long missedReason) {
+ AddCallParams.AddCallParametersBuilder builder =
+ new AddCallParams.AddCallParametersBuilder();
+ builder.setCallerInfo(ci);
+ builder.setNumber(number);
+ builder.setPostDialDigits(postDialDigits);
+ builder.setViaNumber(viaNumber);
+ builder.setPresentation(presentation);
+ builder.setCallType(callType);
+ builder.setFeatures(features);
+ builder.setAccountHandle(accountHandle);
+ builder.setStart(start);
+ builder.setDuration(duration);
+ builder.setDataUsage(dataUsage == null ? Long.MIN_VALUE : dataUsage);
+ builder.setAddForAllUsers(addForAllUsers);
+ builder.setUserToBeInsertedTo(userToBeInsertedTo);
+ builder.setIsRead(isRead);
+ builder.setCallBlockReason(callBlockReason);
+ builder.setCallScreeningAppName(callScreeningAppName);
+ builder.setCallScreeningComponentName(callScreeningComponentName);
+ builder.setMissedReason(missedReason);
+
+ return addCall(context, builder.build());
+ }
+
+ /**
+ * Adds a call to the call log, using the provided parameters
+ * @result The URI of the call log entry belonging to the user that made or received this
+ * call. This could be of the shadow provider. Do not return it to non-system apps,
+ * as they don't have permissions.
+ * @hide
+ */
+ public static @NonNull Uri addCall(
+ @NonNull Context context, @NonNull AddCallParams params) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
- number, userToBeInsertedTo, addForAllUsers));
+ params.mNumber, params.mUserToBeInsertedTo, params.mAddForAllUsers));
}
final ContentResolver resolver = context.getContentResolver();
- String accountAddress = getLogAccountAddress(context, accountHandle);
+ String accountAddress = getLogAccountAddress(context, params.mAccountHandle);
- int numberPresentation = getLogNumberPresentation(number, presentation);
- String name = (ci != null) ? ci.getName() : "";
+ int numberPresentation = getLogNumberPresentation(params.mNumber, params.mPresentation);
+ String name = (params.mCallerInfo != null) ? params.mCallerInfo.getName() : "";
if (numberPresentation != PRESENTATION_ALLOWED) {
- number = "";
- if (ci != null) {
+ params.mNumber = "";
+ if (params.mCallerInfo != null) {
name = "";
}
}
@@ -1204,41 +1630,46 @@
// accountHandle information
String accountComponentString = null;
String accountId = null;
- if (accountHandle != null) {
- accountComponentString = accountHandle.getComponentName().flattenToString();
- accountId = accountHandle.getId();
+ if (params.mAccountHandle != null) {
+ accountComponentString = params.mAccountHandle.getComponentName().flattenToString();
+ accountId = params.mAccountHandle.getId();
}
- ContentValues values = new ContentValues(6);
+ ContentValues values = new ContentValues(14);
- values.put(NUMBER, number);
- values.put(POST_DIAL_DIGITS, postDialDigits);
- values.put(VIA_NUMBER, viaNumber);
+ values.put(NUMBER, params.mNumber);
+ values.put(POST_DIAL_DIGITS, params.mPostDialDigits);
+ values.put(VIA_NUMBER, params.mViaNumber);
values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
- values.put(TYPE, Integer.valueOf(callType));
- values.put(FEATURES, features);
- values.put(DATE, Long.valueOf(start));
- values.put(DURATION, Long.valueOf(duration));
- if (dataUsage != null) {
- values.put(DATA_USAGE, dataUsage);
+ values.put(TYPE, Integer.valueOf(params.mCallType));
+ values.put(FEATURES, params.mFeatures);
+ values.put(DATE, Long.valueOf(params.mStart));
+ values.put(DURATION, Long.valueOf(params.mDuration));
+ if (params.mDataUsage != Long.MIN_VALUE) {
+ values.put(DATA_USAGE, params.mDataUsage);
}
values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
values.put(NEW, Integer.valueOf(1));
values.put(CACHED_NAME, name);
- values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
+ values.put(ADD_FOR_ALL_USERS, params.mAddForAllUsers ? 1 : 0);
- if (callType == MISSED_TYPE) {
- values.put(IS_READ, Integer.valueOf(isRead ? 1 : 0));
+ if (params.mCallType == MISSED_TYPE) {
+ values.put(IS_READ, Integer.valueOf(params.mIsRead ? 1 : 0));
}
- values.put(BLOCK_REASON, callBlockReason);
- values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
- values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
- values.put(MISSED_REASON, Long.valueOf(missedReason));
+ values.put(BLOCK_REASON, params.mCallBlockReason);
+ values.put(CALL_SCREENING_APP_NAME, charSequenceToString(params.mCallScreeningAppName));
+ values.put(CALL_SCREENING_COMPONENT_NAME, params.mCallScreeningComponentName);
+ values.put(MISSED_REASON, Long.valueOf(params.mMissedReason));
+ values.put(PRIORITY, params.mPriority);
+ values.put(SUBJECT, params.mSubject);
+ if (params.mPictureUri != null) {
+ values.put(COMPOSER_PHOTO_URI, params.mPictureUri.toString());
+ }
- if ((ci != null) && (ci.getContactId() > 0)) {
+ if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) {
// Update usage information for the number associated with the contact ID.
// We need to use both the number and the ID for obtaining a data ID since other
// contacts may have the same number.
@@ -1247,23 +1678,23 @@
// We should prefer normalized one (probably coming from
// Phone.NORMALIZED_NUMBER column) first. If it isn't available try others.
- if (ci.normalizedNumber != null) {
- final String normalizedPhoneNumber = ci.normalizedNumber;
+ if (params.mCallerInfo.normalizedNumber != null) {
+ final String normalizedPhoneNumber = params.mCallerInfo.normalizedNumber;
cursor = resolver.query(Phone.CONTENT_URI,
new String[] { Phone._ID },
Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
- new String[] { String.valueOf(ci.getContactId()),
+ new String[] { String.valueOf(params.mCallerInfo.getContactId()),
normalizedPhoneNumber},
null);
} else {
- final String phoneNumber = ci.getPhoneNumber() != null
- ? ci.getPhoneNumber() : number;
+ final String phoneNumber = params.mCallerInfo.getPhoneNumber() != null
+ ? params.mCallerInfo.getPhoneNumber() : params.mNumber;
cursor = resolver.query(
Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
Uri.encode(phoneNumber)),
new String[] { Phone._ID },
Phone.CONTACT_ID + " =?",
- new String[] { String.valueOf(ci.getContactId()) },
+ new String[] { String.valueOf(params.mCallerInfo.getContactId()) },
null);
}
@@ -1272,10 +1703,10 @@
if (cursor.getCount() > 0 && cursor.moveToFirst()) {
final String dataId = cursor.getString(0);
updateDataUsageStatForData(resolver, dataId);
- if (duration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS
- && callType == Calls.OUTGOING_TYPE
- && TextUtils.isEmpty(ci.normalizedNumber)) {
- updateNormalizedNumber(context, resolver, dataId, number);
+ if (params.mDuration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS
+ && params.mCallType == Calls.OUTGOING_TYPE
+ && TextUtils.isEmpty(params.mCallerInfo.normalizedNumber)) {
+ updateNormalizedNumber(context, resolver, dataId, params.mNumber);
}
}
} finally {
@@ -1311,7 +1742,18 @@
final UserManager userManager = context.getSystemService(UserManager.class);
final int currentUserId = userManager.getUserHandle();
- if (addForAllUsers) {
+ if (params.mAddForAllUsers) {
+ if (userManager.isUserUnlocked(UserHandle.SYSTEM)) {
+ // If the user is unlocked, insert to the location provider if a location is
+ // provided. Do not store location if the device is still locked -- this
+ // puts it into device-encrypted storage instead of credential-encrypted
+ // storage.
+ Uri locationUri = maybeInsertLocation(params, resolver, UserHandle.SYSTEM);
+ if (locationUri != null) {
+ values.put(Calls.LOCATION, locationUri.toString());
+ }
+ }
+
// First, insert to the system user.
final Uri uriForSystem = addEntryAndRemoveExpiredEntries(
context, userManager, UserHandle.SYSTEM, values);
@@ -1351,6 +1793,12 @@
// start.
if (userManager.isUserRunning(userHandle)
&& userManager.isUserUnlocked(userHandle)) {
+ Uri locationUri = maybeInsertLocation(params, resolver, userHandle);
+ if (locationUri != null) {
+ values.put(Calls.LOCATION, locationUri.toString());
+ } else {
+ values.put(Calls.LOCATION, (String) null);
+ }
final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager,
userHandle, values);
if (userId == currentUserId) {
@@ -1361,10 +1809,20 @@
} else {
// Single-user entry. Just write to that user, assuming it's running. If the
// user is encrypted, we write to the shadow calllog.
-
- final UserHandle targetUserHandle = userToBeInsertedTo != null
- ? userToBeInsertedTo
+ final UserHandle targetUserHandle = params.mUserToBeInsertedTo != null
+ ? params.mUserToBeInsertedTo
: UserHandle.of(currentUserId);
+
+ if (userManager.isUserRunning(targetUserHandle)
+ && userManager.isUserUnlocked(targetUserHandle)) {
+ Uri locationUri = maybeInsertLocation(params, resolver, targetUserHandle);
+ if (locationUri != null) {
+ values.put(Calls.LOCATION, locationUri.toString());
+ } else {
+ values.put(Calls.LOCATION, (String) null);
+ }
+ }
+
result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle,
values);
}
@@ -1481,6 +1939,27 @@
}
}
+ private static Uri maybeInsertLocation(AddCallParams params, ContentResolver resolver,
+ UserHandle user) {
+ if (Double.isNaN(params.mLatitude) || Double.isNaN(params.mLongitude)) {
+ return null;
+ }
+ ContentValues locationValues = new ContentValues();
+ locationValues.put(Locations.LATITUDE, params.mLatitude);
+ locationValues.put(Locations.LONGITUDE, params.mLongitude);
+ Uri locationUri = ContentProvider.maybeAddUserId(Locations.CONTENT_URI,
+ user.getIdentifier());
+ try {
+ return resolver.insert(locationUri, locationValues);
+ } catch (SecurityException e) {
+ // This can happen if the caller doesn't have location permissions. If that's the
+ // case just skip the insertion.
+ Log.w(LOG_TAG, "Skipping inserting location because caller lacks"
+ + " ACCESS_FINE_LOCATION.");
+ return null;
+ }
+ }
+
private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) {
final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
.appendPath(dataId)
@@ -1587,4 +2066,47 @@
return missedReason >= (USER_MISSED_NO_ANSWER);
}
}
+
+ /**
+ * Table that contains information on location data sent via call composer.
+ *
+ * All fields in this table require the {@link Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission for access.
+ */
+ public static class Locations implements BaseColumns {
+ private Locations() {}
+ /**
+ * Authority for the locations content provider.
+ */
+ public static final String AUTHORITY = "call_composer_locations";
+
+ /**
+ * Content type for the location table.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_composer_location";
+
+ /**
+ * Content type for the location entries.
+ */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/call_composer_location";
+
+ /**
+ * The content URI for this table
+ */
+ @NonNull
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * Latitude in degrees. See {@link android.location.Location#setLatitude(double)}.
+ * <p>Type: REAL</p>
+ */
+ public static final String LATITUDE = "latitude";
+
+ /**
+ * Longitude in degrees. See {@link android.location.Location#setLongitude(double)}.
+ * <p>Type: REAL</p>
+ */
+ public static final String LONGITUDE = "longitude";
+ }
}
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 4ead3fc..e0f3018 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -56,5 +56,6 @@
void onNotificationDirectReply(String key);
void onSuggestedReplySent(String key, in CharSequence reply, int source);
void onActionClicked(String key, in Notification.Action action, int source);
+ void onNotificationClicked(String key);
void onAllowedAdjustmentsChanged();
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 6320149..cf2152c 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -242,6 +242,13 @@
}
/**
+ * Implement this to know when a notification is clicked by user.
+ * @param key the notification key
+ */
+ public void onNotificationClicked(@NonNull String key) {
+ }
+
+ /**
* Implement this to know when a user has changed which features of
* their notifications the assistant can modify.
* <p> Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what
@@ -422,6 +429,13 @@
}
@Override
+ public void onNotificationClicked(String key) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = key;
+ mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_CLICKED, args).sendToTarget();
+ }
+
+ @Override
public void onAllowedAdjustmentsChanged() {
mHandler.obtainMessage(MyHandler.MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED).sendToTarget();
}
@@ -445,6 +459,7 @@
public static final int MSG_ON_PANEL_REVEALED = 9;
public static final int MSG_ON_PANEL_HIDDEN = 10;
public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
+ public static final int MSG_ON_NOTIFICATION_CLICKED = 12;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -550,6 +565,13 @@
onNotificationVisibilityChanged(key, isVisible);
break;
}
+ case MSG_ON_NOTIFICATION_CLICKED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ String key = (String) args.arg1;
+ args.recycle();
+ onNotificationClicked(key);
+ break;
+ }
}
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index f79b59f..01e5260 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1487,6 +1487,11 @@
}
@Override
+ public void onNotificationClicked(String key) {
+ // no-op in the listener
+ }
+
+ @Override
public void onAllowedAdjustmentsChanged() {
// no-op in the listener
}
diff --git a/core/java/android/service/textservice/OWNERS b/core/java/android/service/textservice/OWNERS
index 10b8b76..0471e29 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/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 63ee927..aa05787 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -225,7 +225,7 @@
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 @@
*
* @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 @@
* @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 7ac57b5..f603ef7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1193,11 +1193,9 @@
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 @@
.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 d694962..96d7304 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1852,20 +1852,22 @@
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 @@
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/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0a4b784..5140c09 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2926,7 +2926,10 @@
? 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 @@
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 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 @@
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 582be8d..0471e29 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/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 6bc3110..f6cd7e2 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -323,6 +323,21 @@
}
/**
+ * Sets to containers adjacent to each other. Containers below two visible adjacent roots will
+ * be made invisible. This currently only applies to Task containers created by organizer.
+ * @param root1 the first root.
+ * @param root2 the second root.
+ */
+ @NonNull
+ public WindowContainerTransaction setAdjacentRoots(
+ @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
+ mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
+ root1.asBinder(),
+ root2.asBinder()));
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -642,6 +657,7 @@
public static final int HIERARCHY_OP_TYPE_REORDER = 1;
public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2;
public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT = 3;
+ public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4;
private final int mType;
@@ -680,6 +696,11 @@
container, null, windowingModes, activityTypes, false);
}
+ public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
+ return new HierarchyOp(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
+ root1, root2, null, null, false);
+ }
+
private HierarchyOp(int type, @NonNull IBinder container, @Nullable IBinder reparent,
int[] windowingModes, int[] activityTypes, boolean toTop) {
mType = type;
@@ -728,6 +749,11 @@
return mContainer;
}
+ @NonNull
+ public IBinder getAdjacentRoot() {
+ return mReparent;
+ }
+
public boolean getToTop() {
return mToTop;
}
@@ -756,6 +782,9 @@
+ mReparent + "}";
case HIERARCHY_OP_TYPE_REORDER:
return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+ return "{SetAdjacentRoot: container=" + mContainer
+ + " adjacentRoot=" + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index cc266d6..a5eb5f6 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -22,17 +22,16 @@
parcelable CompatibilityChangeConfig;
parcelable CompatibilityChangeInfo;
-
/**
* Platform private API for talking with the PlatformCompat service.
*
- * <p> Should be used for gating and logging from non-app processes.
- * For app processes please use android.compat.Compatibility API.
+ * <p>Should be used for gating and logging from non-app processes.
+ *
+ * <p>Note: for app processes please use {@code android.compat.Compatibility} API.
*
* {@hide}
*/
-interface IPlatformCompat
-{
+interface IPlatformCompat {
/**
* Reports that a compatibility change is affecting an app process now.
@@ -40,8 +39,9 @@
* <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)},
* you do not need to call this API directly. The change will be reported for you.
*
- * @param changeId The ID of the compatibility change taking effect.
- * @param appInfo Representing the affected app.
+ * @param changeId the ID of the compatibility change taking effect
+ * @param appInfo representing the affected app
+ * @throws SecurityException if logging is not allowed
*/
void reportChange(long changeId, in ApplicationInfo appInfo);
@@ -51,11 +51,12 @@
* <p>Note: for changes that are gated using {@link #isChangeEnabled(long, String)},
* you do not need to call this API directly. The change will be reported for you.
*
- * @param changeId The ID of the compatibility change taking effect.
- * @param userId The ID of the user that the operation is done for.
- * @param packageName The package name of the app in question.
+ * @param changeId the ID of the compatibility change taking effect
+ * @param userId the ID of the user that the operation is done for
+ * @param packageName the package name of the app in question
+ * @throws SecurityException if logging is not allowed
*/
- void reportChangeByPackageName(long changeId, in String packageName, int userId);
+ void reportChangeByPackageName(long changeId, in String packageName, int userId);
/**
* Reports that a compatibility change is affecting an app process now.
@@ -63,13 +64,14 @@
* <p>Note: for changes that are gated using {@link #isChangeEnabled(long, int)},
* you do not need to call this API directly. The change will be reported for you.
*
- * @param changeId The ID of the compatibility change taking effect.
- * @param uid The UID of the app in question.
+ * @param changeId the ID of the compatibility change taking effect
+ * @param uid the UID of the app in question
+ * @throws SecurityException if logging is not allowed
*/
void reportChangeByUid(long changeId, int uid);
/**
- * Query if a given compatibility change is enabled for an app process. This method should
+ * Queries if a given compatibility change is enabled for an app process. This method should
* be called when implementing functionality on behalf of the affected app.
*
* <p>If this method returns {@code true}, the calling code should implement the compatibility
@@ -79,14 +81,15 @@
* <p>It will also report the change as {@link #reportChange(long, ApplicationInfo)} would, so
* there is no need to call that method directly.
*
- * @param changeId The ID of the compatibility change in question.
- * @param appInfo Representing the app in question.
- * @return {@code true} if the change is enabled for the current app.
+ * @param changeId the ID of the compatibility change in question
+ * @param appInfo representing the app in question
+ * @return {@code true} if the change is enabled for the current app
+ * @throws SecurityException if logging or reading compat confis is not allowed
*/
boolean isChangeEnabled(long changeId, in ApplicationInfo appInfo);
/**
- * Query if a given compatibility change is enabled for an app process. This method should
+ * Queries if a given compatibility change is enabled for an app process. This method should
* be called when implementing functionality on behalf of the affected app.
*
* <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
@@ -102,15 +105,16 @@
* <p>It will also report the change as {@link #reportChange(long, String)} would, so there is
* no need to call that method directly.
*
- * @param changeId The ID of the compatibility change in question.
- * @param packageName The package name of the app in question.
- * @param userId The ID of the user that the operation is done for.
- * @return {@code true} if the change is enabled for the current app.
+ * @param changeId the ID of the compatibility change in question
+ * @param packageName the package name of the app in question
+ * @param userId the ID of the user that the operation is done for
+ * @return {@code true} if the change is enabled for the current app
+ * @throws SecurityException if logging or reading compat confis is not allowed
*/
boolean isChangeEnabledByPackageName(long changeId, in String packageName, int userId);
/**
- * Query if a given compatibility change is enabled for an app process. This method should
+ * Queries if a given compatibility change is enabled for an app process. This method should
* be called when implementing functionality on behalf of the affected app.
*
* <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a uid
@@ -127,110 +131,132 @@
* <p>It will also report the change as {@link #reportChange(long, int)} would, so there is
* no need to call that method directly.
*
- * @param changeId The ID of the compatibility change in question.
- * @param uid The UID of the app in question.
- * @return {@code true} if the change is enabled for the current app.
+ * @param changeId the ID of the compatibility change in question
+ * @param uid the UID of the app in question
+ * @return {@code true} if the change is enabled for the current app
+ * @throws SecurityException if logging or reading compat confis is not allowed
*/
boolean isChangeEnabledByUid(long changeId, int uid);
/**
- * Add overrides to compatibility changes. Kills the app to allow the changes to take effect.
+ * Adds overrides to compatibility changes.
*
- * @param overrides Parcelable containing the compat change overrides to be applied.
- * @param packageName The package name of the app whose changes will be overridden.
+ * <p>Kills the app to allow the changes to take effect.
*
+ * @param overrides parcelable containing the compat change overrides to be applied
+ * @param packageName the package name of the app whose changes will be overridden
+ * @throws SecurityException if overriding changes is not permitted
*/
void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
/**
- * Add overrides to compatibility changes. Doesn't kill the app, to be only used in tests.
+ * Adds overrides to compatibility changes.
*
- * @param overrides Parcelable containing the compat change overrides to be applied.
- * @param packageName The package name of the app whose changes will be overridden.
+ * <p>Does not kill the app, to be only used in tests.
*
+ * @param overrides parcelable containing the compat change overrides to be applied
+ * @param packageName the package name of the app whose changes will be overridden
+ * @throws SecurityException if overriding changes is not permitted.
*/
void setOverridesForTest(in CompatibilityChangeConfig overrides, in String packageName);
/**
- * Removes an override previously added via {@link #setOverrides(CompatibilityChangeConfig,
- * String)}. This restores the default behaviour for the given change and app, once any app
- * processes have been restarted.
- * Kills the app to allow the changes to take effect.
+ * Restores the default behaviour for the given change and app.
*
- * @param changeId The ID of the change that was overridden.
- * @param packageName The app package name that was overridden.
- * @return {@code true} if an override existed;
+ * <p>Kills the app to allow the changes to take effect.
+ *
+ * @param changeId the ID of the change that was overridden
+ * @param packageName the app package name that was overridden
+ * @return {@code true} if an override existed
+ * @throws SecurityException if overriding changes is not permitted
*/
boolean clearOverride(long changeId, String packageName);
/**
- * Enable all compatibility changes which have enabledSinceTargetSdk ==
- * {@param targetSdkVersion} for an app, subject to the policy. Kills the app to allow the
- * changes to take effect.
+ * Restores the default behaviour for the given change and app.
*
- * @param packageName The package name of the app whose compatibility changes will be enabled.
+ * <p>Does not kill the app; to be only used in tests.
+ *
+ * @param changeId the ID of the change that was overridden
+ * @param packageName the app package name that was overridden
+ * @throws SecurityException if overriding changes is not permitted
+ */
+ void clearOverrideForTest(long changeId, String packageName);
+
+ /**
+ * Enables all compatibility changes that have enabledSinceTargetSdk ==
+ * {@param targetSdkVersion} for an app, subject to the policy.
+ *
+ * <p>Kills the app to allow the changes to take effect.
+ *
+ * @param packageName The package name of the app whose compatibility changes will be
+ * enabled.
* @param targetSdkVersion The targetSdkVersion for filtering the changes to be enabled.
- *
* @return The number of changes that were enabled.
+ * @throws SecurityException if overriding changes is not permitted.
*/
int enableTargetSdkChanges(in String packageName, int targetSdkVersion);
/**
- * Disable all compatibility changes which have enabledAfterTargetSdk ==
- * {@param targetSdkVersion} for an app, subject to the policy. Kills the app to allow the
- * changes to take effect.
+ * Disables all compatibility changes that have enabledAfterTargetSdk ==
+ * {@param targetSdkVersion} for an app, subject to the policy.
*
- * @param packageName The package name of the app whose compatibility changes will be disabled.
- * @param targetSdkVersion The targetSdkVersion for filtering the changes to be disabled.
+ * <p>Kills the app to allow the changes to take effect.
*
- * @return The number of changes that were disabled.
+ * @param packageName the package name of the app whose compatibility changes will be
+ * disabled
+ * @param targetSdkVersion the targetSdkVersion for filtering the changes to be disabled
+ * @return the number of changes that were disabled
+ * @throws SecurityException if overriding changes is not permitted.
*/
int disableTargetSdkChanges(in String packageName, int targetSdkVersion);
/**
- * Revert overrides to compatibility changes. Kills the app to allow the changes to take effect.
+ * Restores the default behaviour for the given app.
*
- * @param packageName The package name of the app whose overrides will be cleared.
+ * <p>Kills the app to allow the changes to take effect.
*
+ * @param packageName the package name of the app whose overrides will be cleared
+ * @throws SecurityException if overriding changes is not permitted
*/
void clearOverrides(in String packageName);
/**
- * Revert overrides to compatibility changes. Doesn't kill the app, to be only used in tests.
+ * Restores the default behaviour for the given app.
*
- * @param packageName The package name of the app whose overrides will be cleared.
+ * <p>Does not kill the app; to be only used in tests.
*
+ * @param packageName the package name of the app whose overrides will be cleared
+ * @throws SecurityException if overriding changes is not permitted
*/
void clearOverridesForTest(in String packageName);
-
/**
* Get configs for an application.
*
- * @param appInfo The application whose config will be returned.
- *
- * @return A {@link CompatibilityChangeConfig}, representing whether a change is enabled for
- * the given app or not.
+ * @param appInfo the application whose config will be returned
+ * @return a {@link CompatibilityChangeConfig}, representing whether a change is enabled for
+ * the given app or not
*/
CompatibilityChangeConfig getAppConfig(in ApplicationInfo appInfo);
/**
* List all compatibility changes.
*
- * @return An array of {@link CompatChangeInfo} known to the service.
+ * @return an array of {@link CompatibilityChangeInfo} known to the service
*/
CompatibilityChangeInfo[] listAllChanges();
/**
- * List the compatibility changes that should be present in the UI.
- * Filters out certain changes like e.g. logging only.
- *
- * @return An array of {@link CompatChangeInfo}.
- */
+ * List the compatibility changes that should be present in the UI.
+ * Filters out certain changes like e.g. logging only.
+ *
+ * @return an array of {@link CompatibilityChangeInfo}
+ */
CompatibilityChangeInfo[] listUIChanges();
/**
- * Get an instance that can determine whether a changeid can be overridden for a package name.
+ * Gets an instance that can determine whether a changeid can be overridden for a package name.
*/
IOverrideValidator getOverrideValidator();
}
diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java
index e9e39db..2113173 100644
--- a/core/java/com/android/internal/inputmethod/CallbackUtils.java
+++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java
@@ -205,14 +205,14 @@
* 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/textservice/OWNERS b/core/java/com/android/internal/textservice/OWNERS
new file mode 100644
index 0000000..0471e29
--- /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 1fadfc5..b42404f 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.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 @@
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 @@
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/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9712b4e..a161f18 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -307,8 +307,24 @@
return getDevicePolicyManager().getPasswordMaximumLength(quality);
}
+ /**
+ * Returns aggregated (legacy) password quality requirement on the target user from all admins.
+ */
public PasswordMetrics getRequestedPasswordMetrics(int userId) {
- return getDevicePolicyManager().getPasswordMinimumMetrics(userId);
+ return getRequestedPasswordMetrics(userId, false);
+ }
+
+ /**
+ * Returns aggregated (legacy) password quality requirement on the target user from all admins,
+ * optioanlly disregarding policies set on the managed profile as if the profile had separate
+ * work challenge.
+ */
+ public PasswordMetrics getRequestedPasswordMetrics(int userId, boolean deviceWideOnly) {
+ return getDevicePolicyManager().getPasswordMinimumMetrics(userId, deviceWideOnly);
+ }
+
+ private int getRequestedPasswordHistoryLength(int userId) {
+ return getDevicePolicyManager().getPasswordHistoryLength(null, userId);
}
/**
@@ -317,39 +333,21 @@
* @return complexity level for the user.
*/
public @DevicePolicyManager.PasswordComplexity int getRequestedPasswordComplexity(int userId) {
- return getDevicePolicyManager().getAggregatedPasswordComplexityForUser(userId);
+ return getRequestedPasswordComplexity(userId, false);
}
- public int getRequestedPasswordQuality(int userId) {
- return getDevicePolicyManager().getPasswordQuality(null, userId);
- }
+ /**
+ * Returns the effective complexity for the user, optioanlly disregarding complexity set on the
+ * managed profile as if the profile had separate work challenge.
- private int getRequestedPasswordHistoryLength(int userId) {
- return getDevicePolicyManager().getPasswordHistoryLength(null, userId);
- }
-
- public int getRequestedPasswordMinimumLetters(int userId) {
- return getDevicePolicyManager().getPasswordMinimumLetters(null, userId);
- }
-
- public int getRequestedPasswordMinimumUpperCase(int userId) {
- return getDevicePolicyManager().getPasswordMinimumUpperCase(null, userId);
- }
-
- public int getRequestedPasswordMinimumLowerCase(int userId) {
- return getDevicePolicyManager().getPasswordMinimumLowerCase(null, userId);
- }
-
- public int getRequestedPasswordMinimumNumeric(int userId) {
- return getDevicePolicyManager().getPasswordMinimumNumeric(null, userId);
- }
-
- public int getRequestedPasswordMinimumSymbols(int userId) {
- return getDevicePolicyManager().getPasswordMinimumSymbols(null, userId);
- }
-
- public int getRequestedPasswordMinimumNonLetter(int userId) {
- return getDevicePolicyManager().getPasswordMinimumNonLetter(null, userId);
+ * @param userId The user to return the complexity for.
+ * @param deviceWideOnly whether to ignore complexity set on the managed profile.
+ * @return complexity level for the user.
+ */
+ public @DevicePolicyManager.PasswordComplexity int getRequestedPasswordComplexity(int userId,
+ boolean deviceWideOnly) {
+ return getDevicePolicyManager().getAggregatedPasswordComplexityForUser(userId,
+ deviceWideOnly);
}
@UnsupportedAppUsage
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index a761b4c..e5bc470 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -94,6 +94,9 @@
// property for runtime configuration differentiation in vendor
private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku";
+ // property for background blur support in surface flinger
+ private static final String BLUR_PROPERTY = "ro.surface_flinger.supports_background_blur";
+
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
int[] mGlobalGids = EmptyArray.INT;
@@ -1242,6 +1245,10 @@
addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0);
}
+ if (SystemProperties.get(BLUR_PROPERTY, "default").equals("1")) {
+ addFeature(PackageManager.FEATURE_CROSS_LAYER_BLUR, 0);
+ }
+
for (String featureName : mUnavailableFeatures) {
removeFeature(featureName);
}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 8d3faf4..b790056 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -68,7 +68,7 @@
};
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 @@
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 @@
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 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 b8e1807..7c670e1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1593,7 +1593,9 @@
}
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 @@
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 @@
(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/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 4dc8121..c76a5d3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1533,7 +1533,6 @@
jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name,
jstring managed_nice_name, fail_fn_t fail_fn) {
- ensureInAppMountNamespace(fail_fn);
std::vector<std::string> merged_data_info_list;
insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list,
process_name, managed_nice_name, fail_fn);
@@ -1680,10 +1679,11 @@
MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn);
- // System services, isolated process, webview/app zygote, old target sdk app, should
- // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
- // Isolated process / webview / app zygote should be gated by SELinux and file permission
- // so they can't even traverse CE / DE directories.
+ // Make sure app is running in its own mount namespace before isolating its data directories.
+ ensureInAppMountNamespace(fail_fn);
+
+ // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind
+ // mount all related packages separately.
if (mount_data_dirs) {
isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list,
uid, process_name, managed_nice_name, fail_fn);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9579ed..76acc00 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -322,8 +322,10 @@
<protected-broadcast android:name="android.net.nsd.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" />
<protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+ <protected-broadcast android:name="android.nfc.action.REQUIRE_UNLOCK_FOR_NFC" />
<protected-broadcast android:name="com.android.nfc.action.LLCP_UP" />
<protected-broadcast android:name="com.android.nfc.action.LLCP_DOWN" />
<protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
@@ -434,6 +436,8 @@
<protected-broadcast android:name="android.location.PROVIDERS_CHANGED" />
<protected-broadcast android:name="android.location.MODE_CHANGED" />
+ <protected-broadcast android:name="android.location.action.GNSS_CAPABILITIES_CHANGED" />
+
<protected-broadcast android:name="android.net.proxy.PAC_REFRESH" />
<protected-broadcast android:name="android.telecom.action.DEFAULT_DIALER_CHANGED" />
@@ -1691,7 +1695,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.
-->
@@ -5383,6 +5387,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
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_camera_blocked.xml
rename to 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
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_mic_blocked.xml
rename to core/res/res/drawable/ic_mic_blocked.xml
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index b558087..e46147e 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 272e130..7f6053e 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>
@@ -4001,13 +4010,13 @@
<string name="config_misprovisionedBrandValue" translatable="false"></string>
<!-- Pre-scale volume at volume step 1 for Absolute Volume -->
- <fraction name="config_prescaleAbsoluteVolume_index1">50%</fraction>
+ <fraction name="config_prescaleAbsoluteVolume_index1">60%</fraction>
<!-- Pre-scale volume at volume step 2 for Absolute Volume -->
- <fraction name="config_prescaleAbsoluteVolume_index2">70%</fraction>
+ <fraction name="config_prescaleAbsoluteVolume_index2">80%</fraction>
<!-- Pre-scale volume at volume step 3 for Absolute Volume -->
- <fraction name="config_prescaleAbsoluteVolume_index3">85%</fraction>
+ <fraction name="config_prescaleAbsoluteVolume_index3">90%</fraction>
<!-- Whether or not the "SMS app service" feature is enabled -->
<bool name="config_useSmsAppService">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 317a76f..996fbb3 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 @@
<!-- 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 0c86905..e2271d1 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/view/textservice/OWNERS b/core/tests/coretests/src/android/view/textservice/OWNERS
new file mode 100644
index 0000000..0471e29
--- /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/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 6019b90..597ea14 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -366,15 +366,6 @@
}
@Override
- public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
- }
-
- @Override
- public boolean isHdmiCecVolumeControlEnabled() {
- return true;
- }
-
- @Override
public void addHdmiCecVolumeControlFeatureListener(
IHdmiCecVolumeControlFeatureListener listener) {
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index eb5e0a2..c3d822c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -462,6 +462,8 @@
<permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
<!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
+ <!-- Permission required for CTS test - CallLogTest -->
+ <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4914336..3aaf11c 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -28,11 +28,12 @@
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 @@
/** 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 @@
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/Rect.java b/graphics/java/android/graphics/Rect.java
index 87ca960..7e68bc0 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -19,6 +19,7 @@
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -702,6 +703,40 @@
}
/**
+ * Splits this Rect into small rects of the same width.
+ * @hide
+ */
+ @TestApi
+ public void splitVertically(@NonNull Rect ...splits) {
+ final int count = splits.length;
+ final int splitWidth = width() / count;
+ for (int i = 0; i < count; i++) {
+ final Rect split = splits[i];
+ split.left = left + (splitWidth * i);
+ split.top = top;
+ split.right = split.left + splitWidth;
+ split.bottom = bottom;
+ }
+ }
+
+ /**
+ * Splits this Rect into small rects of the same height.
+ * @hide
+ */
+ @TestApi
+ public void splitHorizontally(@NonNull Rect ...outSplits) {
+ final int count = outSplits.length;
+ final int splitHeight = height() / count;
+ for (int i = 0; i < count; i++) {
+ final Rect split = outSplits[i];
+ split.left = left;
+ split.top = top + (splitHeight * i);
+ split.right = right;
+ split.bottom = split.top + splitHeight;
+ }
+ }
+
+ /**
* Parcelable interface methods
*/
@Override
diff --git a/keystore/java/android/security/AuthTokenUtils.java b/keystore/java/android/security/AuthTokenUtils.java
index 14d6626..e637600 100644
--- a/keystore/java/android/security/AuthTokenUtils.java
+++ b/keystore/java/android/security/AuthTokenUtils.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.hardware.security.keymint.HardwareAuthToken;
-import android.hardware.security.keymint.Timestamp;
+import android.hardware.security.secureclock.Timestamp;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 1fde2b5..fcc518c 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 @@
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/res/layout/bubble_overflow_activity.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
similarity index 93%
rename from libs/WindowManager/Shell/res/layout/bubble_overflow_activity.xml
rename to libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
index 3060619..8224d95 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_activity.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
@@ -14,7 +14,7 @@
~ limitations under the License
-->
-<LinearLayout
+<com.android.wm.shell.bubbles.BubbleOverflowContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_overflow_container"
android:layout_width="match_parent"
@@ -34,8 +34,8 @@
<LinearLayout
android:id="@+id/bubble_overflow_empty_state"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:paddingLeft="@dimen/bubble_overflow_empty_state_padding"
android:paddingRight="@dimen/bubble_overflow_empty_state_padding"
android:orientation="vertical"
@@ -69,4 +69,4 @@
android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding"
android:gravity="center"/>
</LinearLayout>
-</LinearLayout>
+</com.android.wm.shell.bubbles.BubbleOverflowContainerView>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index cb6b543..67f4cd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -24,7 +24,6 @@
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.wm.shell.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -37,14 +36,16 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Outline;
+import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
-import android.os.Bundle;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
@@ -77,6 +78,7 @@
private AlphaOptimizedButton mSettingsIcon;
private TaskView mTaskView;
+ private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -125,6 +127,7 @@
if (mDestroyed || mInitialized) {
return;
}
+
// Custom options so there is no activity transition animation
ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
0 /* enterResId */, 0 /* exitResId */);
@@ -307,15 +310,25 @@
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
*/
- void initialize(BubbleController controller, BubbleStackView stackView) {
+ void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
mController = controller;
mStackView = stackView;
-
- mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
- mExpandedViewContainer.addView(mTaskView);
- bringChildToFront(mTaskView);
- mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
+ mIsOverflow = isOverflow;
mPositioner = mController.getPositioner();
+
+ if (mIsOverflow) {
+ mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
+ R.layout.bubble_overflow_container, null /* root */);
+ mOverflowView.setBubbleController(mController);
+ mExpandedViewContainer.addView(mOverflowView);
+ bringChildToFront(mOverflowView);
+ mSettingsIcon.setVisibility(GONE);
+ } else {
+ mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
+ mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
+ mExpandedViewContainer.addView(mTaskView);
+ bringChildToFront(mTaskView);
+ }
}
void updateDimensions() {
@@ -390,6 +403,17 @@
/** Return a GraphicBuffer with the contents of the task view surface. */
@Nullable
SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
+ if (mIsOverflow) {
+ // For now, just snapshot the view and return it as a hw buffer so that the animation
+ // code for both the tasks and overflow can be the same
+ Picture p = new Picture();
+ mOverflowView.draw(
+ p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight()));
+ p.endRecording();
+ Bitmap snapshot = Bitmap.createBitmap(p);
+ return new SurfaceControl.ScreenshotHardwareBuffer(snapshot.getHardwareBuffer(),
+ snapshot.getColorSpace(), false /* containsSecureLayers */);
+ }
if (mTaskView == null || mTaskView.getSurfaceControl() == null) {
return null;
}
@@ -400,6 +424,11 @@
}
int[] getTaskViewLocationOnScreen() {
+ if (mIsOverflow) {
+ // This is only used for animating away the surface when switching bubbles, just use the
+ // view location on screen for now to allow us to use the same animation code with tasks
+ return mOverflowView.getLocationOnScreen();
+ }
if (mTaskView != null) {
return mTaskView.getLocationOnScreen();
} else {
@@ -450,10 +479,7 @@
final float alpha = visibility ? 1f : 0f;
mPointerView.setAlpha(alpha);
- if (mTaskView == null) {
- return;
- }
- if (alpha != mTaskView.getAlpha()) {
+ if (mTaskView != null) {
mTaskView.setAlpha(alpha);
}
}
@@ -467,19 +493,6 @@
return mTaskId;
}
- public void setOverflow(boolean overflow) {
- mIsOverflow = overflow;
-
- Intent target = new Intent(mContext, BubbleOverflowActivity.class);
- target.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mController));
- target.putExtras(extras);
- mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
- target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mSettingsIcon.setVisibility(GONE);
- }
-
/**
* Sets the bubble used to populate this view.
*/
@@ -511,7 +524,8 @@
if (isNew) {
mPendingIntent = mBubble.getBubbleIntent();
- if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
+ if ((mPendingIntent != null || mBubble.hasMetadataShortcutId())
+ && mTaskView != null) {
setContentVisibility(false);
mTaskView.setVisibility(VISIBLE);
}
@@ -552,13 +566,19 @@
desiredHeight = Math.max(desiredHeight, mMinHeight);
float height = Math.min(desiredHeight, getMaxExpandedHeight());
height = Math.max(height, mMinHeight);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
+ FrameLayout.LayoutParams lp = mIsOverflow
+ ? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams()
+ : (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
mNeedsNewHeight = lp.height != height;
if (!mImeVisible) {
// If the ime is visible... don't adjust the height because that will cause
// a configuration change and the ime will be lost.
lp.height = (int) height;
- mTaskView.setLayoutParams(lp);
+ if (mIsOverflow) {
+ mOverflowView.setLayoutParams(lp);
+ } else {
+ mTaskView.setLayoutParams(lp);
+ }
mNeedsNewHeight = false;
}
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -602,6 +622,9 @@
updateHeight();
mTaskView.onLocationChanged();
}
+ if (mIsOverflow) {
+ mOverflowView.show();
+ }
}
/**
@@ -637,8 +660,8 @@
/**
* Cleans up anything related to the task and TaskView. If this view should be reused after this
- * method is called, then {@link #initialize(BubbleController, BubbleStackView)} must be invoked
- * first.
+ * method is called, then {@link #initialize(BubbleController, BubbleStackView, boolean)} must
+ * be invoked first.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -653,8 +676,6 @@
}
if (mTaskView != null) {
mTaskView.release();
- }
- if (mTaskView != null) {
removeView(mTaskView);
mTaskView = null;
}
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 8ab2f63..16cd3cf 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
@@ -65,8 +65,7 @@
/** Call before use and again if cleanUpExpandedState was called. */
fun initialize(controller: BubbleController) {
- getExpandedView()?.initialize(controller, controller.stackView)
- getExpandedView()?.setOverflow(true)
+ getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
}
fun cleanUpExpandedState() {
@@ -86,7 +85,7 @@
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()
}
@@ -96,7 +95,7 @@
// 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
@@ -106,7 +105,7 @@
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 */)
@@ -116,7 +115,7 @@
// 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()
@@ -177,10 +176,10 @@
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
@@ -191,10 +190,10 @@
}
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/BubbleOverflowActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
similarity index 87%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index cfd0066..39e4e1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -29,16 +29,21 @@
import android.graphics.Color;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -50,10 +55,9 @@
import java.util.function.Consumer;
/**
- * Activity for showing aged out bubbles.
- * Must be public to be accessible to androidx...AppComponentFactory
+ * Container view for showing aged out bubbles.
*/
-public class BubbleOverflowActivity extends Activity {
+public class BubbleOverflowContainerView extends LinearLayout {
static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
@@ -95,37 +99,55 @@
}
}
+ public BubbleOverflowContainerView(Context context) {
+ super(context);
+ }
+
+ public BubbleOverflowContainerView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BubbleOverflowContainerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public BubbleOverflowContainerView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setBubbleController(BubbleController controller) {
+ mController = controller;
+ }
+
+ public void show() {
+ setVisibility(View.VISIBLE);
+ updateOverflow();
+ }
+
+ public void hide() {
+ setVisibility(View.INVISIBLE);
+ }
+
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.bubble_overflow_activity);
+ protected void onFinishInflate() {
+ super.onFinishInflate();
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
mEmptyStateTitle = findViewById(R.id.bubble_overflow_empty_title);
mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
-
- Intent intent = getIntent();
- if (intent != null && intent.getExtras() != null) {
- IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
- if (binder instanceof ObjectWrapper) {
- mController = ((ObjectWrapper<BubbleController>) binder).get();
- updateOverflow();
- }
- } else {
- Log.w(TAG, "Bubble overflow activity created without bubble controller!");
- }
}
void updateOverflow() {
Resources res = getResources();
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
- new NoScrollGridLayoutManager(getApplicationContext(), columns));
+ new NoScrollGridLayoutManager(getContext(), columns));
DisplayMetrics displayMetrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ getContext().getDisplay().getMetrics(displayMetrics);
final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
@@ -137,7 +159,7 @@
- res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
final int viewHeight = recyclerViewHeight / rows;
- mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
+ mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
mController::promoteBubbleFromOverflow,
mController.getPositioner(),
viewWidth, viewHeight);
@@ -146,6 +168,11 @@
mOverflowBubbles.clear();
mOverflowBubbles.addAll(mController.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
+
+ // Currently BubbleExpandedView.mExpandedViewContainer is WRAP_CONTENT so use the same
+ // width we would use for the recycler view
+ LayoutParams lp = (LayoutParams) mEmptyState.getLayoutParams();
+ lp.width = recyclerViewWidth;
updateEmptyStateVisibility();
mController.setOverflowListener(mDataListener);
@@ -172,12 +199,12 @@
? res.getDrawable(R.drawable.bubble_ic_empty_overflow_dark)
: res.getDrawable(R.drawable.bubble_ic_empty_overflow_light));
- findViewById(android.R.id.content)
+ findViewById(R.id.bubble_overflow_container)
.setBackgroundColor(isNightMode
? res.getColor(R.color.bubbles_dark)
: res.getColor(R.color.bubbles_light));
- final TypedArray typedArray = getApplicationContext().obtainStyledAttributes(
+ final TypedArray typedArray = getContext().obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating,
android.R.attr.textColorSecondary});
int bgColor = typedArray.getColor(0, isNightMode ? Color.BLACK : Color.WHITE);
@@ -222,36 +249,6 @@
}
}
};
-
- @Override
- public void onStart() {
- super.onStart();
- }
-
- @Override
- public void onRestart() {
- super.onRestart();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateOverflow();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- public void onDestroy() {
- super.onDestroy();
- }
}
class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index e21ba63..0e7e92d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -138,7 +138,7 @@
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(controller, stackView);
+ info.expandedView.initialize(controller, stackView, false /* isOverflow */);
}
if (b.getShortcutInfo() != null) {
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 b347329..a1b0dbe 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 @@
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 @@
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 b4d7387..9f6dd1f 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 @@
/** 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/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e706f76..801ee2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -59,7 +59,6 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.os.SomeArgs;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -505,8 +504,6 @@
mOnDisplayIdChangeCallback.accept(info.displayId);
}
- mPipMenuController.attach(leash);
-
if (mInSwipePipToHomeTransition) {
final Rect destinationBounds = mPipBoundsState.getBounds();
// animation is finished in the Launcher and here we directly apply the final touch.
@@ -535,6 +532,7 @@
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ mPipMenuController.attach(mLeash);
final Rect sourceHintRect = getValidSourceHintRect(info.pictureInPictureParams,
currentBounds);
scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
@@ -587,6 +585,9 @@
}
private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable) {
+ // PiP menu is attached late in the process here to avoid any artifacts on the leash
+ // caused by addShellRoot when in gesture navigation mode.
+ mPipMenuController.attach(mLeash);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
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 bdac37a..5c1d18e 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
@@ -142,6 +142,15 @@
mTaskOrganizer.applyTransaction(wct);
}
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
private void onStageRootTaskVanished(StageListenerImpl stageListener) {
if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -357,6 +366,7 @@
@Override
public void onRootTaskAppeared() {
mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
}
@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 30f2701..efd42ce 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
@@ -70,9 +70,9 @@
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (!taskInfo.hasParentTask()) {
- mCallbacks.onRootTaskAppeared();
mRootLeash = leash;
mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
mChildrenLeashes.put(taskInfo.taskId, leash);
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index afae502..65721cc 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -78,21 +78,21 @@
@nullable List<GnssAntennaInfo> getGnssAntennaInfos();
- void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, String attributionTag);
+ void registerGnssStatusCallback(in IGnssStatusListener callback, String packageName, @nullable String attributionTag);
void unregisterGnssStatusCallback(in IGnssStatusListener callback);
- void registerGnssNmeaCallback(in IGnssNmeaListener callback, String packageName, String attributionTag);
+ void registerGnssNmeaCallback(in IGnssNmeaListener callback, String packageName, @nullable String attributionTag);
void unregisterGnssNmeaCallback(in IGnssNmeaListener callback);
- void addGnssMeasurementsListener(in GnssMeasurementRequest request, in IGnssMeasurementsListener listener, String packageName, String attributionTag);
+ void addGnssMeasurementsListener(in GnssMeasurementRequest request, in IGnssMeasurementsListener listener, String packageName, @nullable String attributionTag);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections);
- void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, String attributionTag);
+ void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, @nullable String attributionTag);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssBatchSize();
- void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, String attributionTag, String listenerId);
+ void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, @nullable String attributionTag, @nullable String listenerId);
void flushGnssBatch();
void stopGnssBatch();
@@ -101,7 +101,7 @@
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
String getBestProvider(in Criteria criteria, boolean enabledOnly);
ProviderProperties getProviderProperties(String provider);
- boolean isProviderPackage(String provider, String packageName);
+ boolean isProviderPackage(@nullable String provider, String packageName, @nullable String attributionTag);
List<String> getProviderPackages(String provider);
void setExtraLocationControllerPackage(String packageName);
@@ -113,10 +113,10 @@
boolean isLocationEnabledForUser(int userId);
void setLocationEnabledForUser(boolean enabled, int userId);
- void addTestProvider(String name, in ProviderProperties properties, String packageName, String attributionTag);
- void removeTestProvider(String provider, String packageName, String attributionTag);
- void setTestProviderLocation(String provider, in Location location, String packageName, String attributionTag);
- void setTestProviderEnabled(String provider, boolean enabled, String packageName, String attributionTag);
+ void addTestProvider(String name, in ProviderProperties properties, String packageName, @nullable String attributionTag);
+ void removeTestProvider(String provider, String packageName, @nullable String attributionTag);
+ void setTestProviderLocation(String provider, in Location location, String packageName, @nullable String attributionTag);
+ void setTestProviderEnabled(String provider, boolean enabled, String packageName, @nullable String attributionTag);
LocationTime getGnssTimeMillis();
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 0ce1ad0..96ec590 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -72,6 +72,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -1840,17 +1841,13 @@
* otherwise.
*
* @hide
- * @deprecated Prefer {@link #isProviderPackage(String, String)} instead.
+ * @deprecated Prefer {@link #isProviderPackage(String, String, String)} instead.
*/
@Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public boolean isProviderPackage(@NonNull String packageName) {
- try {
- return mService.isProviderPackage(null, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return isProviderPackage(null, packageName, null);
}
/**
@@ -1861,13 +1858,37 @@
* @param provider a provider listed by {@link #getAllProviders()} or null
* @param packageName the package name to test if it is a provider
* @return true if the given arguments correspond to a provider
+ *
+ * @deprecated Use {@link #isProviderPackage(String, String, String)} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public boolean isProviderPackage(@Nullable String provider, @NonNull String packageName) {
+ return isProviderPackage(provider, packageName, null);
+ }
+
+ /**
+ * Returns true if the given provider corresponds to the given package name. If the given
+ * provider is null, this will return true if any provider corresponds to the given package
+ * name and/or attribution tag. If attribution tag is non-null, the provider identity must match
+ * both the given package name and attribution tag.
+ *
+ * @param provider a provider listed by {@link #getAllProviders()} or null
+ * @param packageName the package name to test if it is a provider
+ * @param attributionTag an optional attribution tag within the given package
+ * @return true if the given arguments correspond to a provider
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- public boolean isProviderPackage(@Nullable String provider, @NonNull String packageName) {
+ public boolean isProviderPackage(@Nullable String provider, @NonNull String packageName,
+ @Nullable String attributionTag) {
try {
- return mService.isProviderPackage(provider, packageName);
+ return mService.isProviderPackage(provider, Objects.requireNonNull(packageName),
+ attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1878,7 +1899,7 @@
* and an empty list if no package is associated with the provider.
*
* @hide
- * @deprecated Prefer {@link #isProviderPackage(String, String)} instead.
+ * @deprecated Prefer {@link #isProviderPackage(String, String, String)} instead.
*/
@TestApi
@Deprecated
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index 9dbdede..0bb7dbb 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -201,11 +201,15 @@
return false;
}
CallerIdentity that = (CallerIdentity) o;
- return mUid == that.mUid
+ return equalsIgnoringListenerId(that) && Objects.equals(mListenerId, that.mListenerId);
+ }
+
+ public boolean equalsIgnoringListenerId(CallerIdentity that) {
+ return that != null
+ && mUid == that.mUid
&& mPid == that.mPid
&& mPackageName.equals(that.mPackageName)
- && Objects.equals(mAttributionTag, that.mAttributionTag)
- && Objects.equals(mListenerId, that.mListenerId);
+ && Objects.equals(mAttributionTag, that.mAttributionTag);
}
@Override
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 13bd856..2703ee3 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -1993,6 +1993,33 @@
return signRSANative(this, sessionId, algorithm, wrappedKey, message);
}
+ /**
+ * Query if the crypto scheme requires the use of a secure decoder
+ * to decode data of the given mime type at the default security level.
+ * The default security level is defined as the highest security level
+ * supported on the device.
+ *
+ * @param mime The mime type of the media data
+ */
+ public boolean requiresSecureDecoder(@NonNull String mime) {
+ return requiresSecureDecoder(mime, getMaxSecurityLevel());
+ }
+
+ /**
+ * Query if the crypto scheme requires the use of a secure decoder
+ * to decode data of the given mime type at the given security level.
+ *
+ * @param mime The mime type of the media data
+ * @param level a security level between {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}
+ * and {@link #SECURITY_LEVEL_HW_SECURE_ALL}. Otherwise the special value
+ * {@link #getMaxSecurityLevel()} is also permitted;
+ * use {@link #getMaxSecurityLevel()} to indicate the maximum security level
+ * supported by the device.
+ * @throws IllegalArgumentException if the requested security level is none of the documented
+ * values for the parameter {@code level}.
+ */
+ public native boolean requiresSecureDecoder(@NonNull String mime, @SecurityLevel int level);
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 1249e0d..0bedbd3 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -190,13 +190,16 @@
/** @hide */
public Builder toBuilder() {
- return new Builder()
+ Builder newBuilder = new Builder()
.deviceId(mDeviceId)
.type(mType)
.audioType(mAudioType)
.audioAddress(mAudioAddress)
- .hdmiPortId(mHdmiPortId)
.cableConnectionStatus(mCableConnectionStatus);
+ if (mType == TV_INPUT_TYPE_HDMI) {
+ newBuilder.hdmiPortId(mHdmiPortId);
+ }
+ return newBuilder;
}
public static final class Builder {
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 4f27b197..6141b7f 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -769,10 +769,12 @@
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 f38a29c..babb16b 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -1953,6 +1953,30 @@
return VectorToJByteArray(env, signature);
}
+static jboolean android_media_MediaDrm_requiresSecureDecoder(
+ JNIEnv *env, jobject thiz, jstring jmimeType,
+ jint jSecurityLevel) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+ if (!CheckDrm(env, drm)) {
+ return JNI_FALSE;
+ }
+
+ String8 mimeType;
+ if (jmimeType != NULL) {
+ mimeType = JStringToString8(env, jmimeType);
+ }
+
+ DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
+ if (securityLevel == DrmPlugin::kSecurityLevelUnknown) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level");
+ return JNI_FALSE;
+ }
+
+ if (securityLevel == DrmPlugin::kSecurityLevelMax) {
+ return drm->requiresSecureDecoder(mimeType.c_str());
+ }
+ return drm->requiresSecureDecoder(mimeType.c_str(), securityLevel);
+}
static const JNINativeMethod gMethods[] = {
{ "native_release", "()V", (void *)android_media_MediaDrm_native_release },
@@ -2075,6 +2099,9 @@
{ "getMetricsNative", "()Landroid/os/PersistableBundle;",
(void *)android_media_MediaDrm_native_getMetrics },
+
+ { "requiresSecureDecoder", "(Ljava/lang/String;I)Z",
+ (void *)android_media_MediaDrm_requiresSecureDecoder },
};
int register_android_media_Drm(JNIEnv *env) {
diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp
index 7f3916f..77583b8 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 @@
/////////////// 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 @@
}
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::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::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::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::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();
}
-/////////////// 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 533a996..2465120 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 @@
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 @@
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 @@
* 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 @@
*/
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 f5e3524..498ba0e 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -229,15 +229,15 @@
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::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 8a1181a..733ee6b 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 @@
*/
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/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 3751564..d464587 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -74,9 +74,9 @@
unversioned_until: "current",
}
-cc_fuzz {
- name: "imagedecoder_fuzzer",
- srcs: ["fuzz_imagedecoder.cpp"],
+cc_defaults {
+ name: "imagedecoder_fuzzer_defaults",
+ srcs: ["fuzz/fuzz_imagedecoder.cpp"],
header_libs: ["jni_headers"],
shared_libs: [
"libbinder",
@@ -97,6 +97,22 @@
"allocator_may_return_null = 1",
],
},
- corpus: ["corpus/*"],
host_supported: true,
}
+
+cc_fuzz {
+ name: "imagedecoder_fuzzer",
+ defaults: ["imagedecoder_fuzzer_defaults"],
+ corpus: ["fuzz/corpus/*"],
+}
+
+cc_fuzz {
+ name: "imagedecoder_png_fuzzer",
+ defaults: ["imagedecoder_fuzzer_defaults"],
+ shared_libs: [
+ "libz",
+ ],
+ cflags: [
+ "-DPNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR",
+ ],
+}
diff --git a/native/graphics/jni/corpus/png_test.png b/native/graphics/jni/corpus/png_test.png
deleted file mode 100644
index 5230051..0000000
--- a/native/graphics/jni/corpus/png_test.png
+++ /dev/null
Binary files differ
diff --git a/native/graphics/jni/corpus/baseline_jpeg.jpg b/native/graphics/jni/fuzz/corpus/baseline_jpeg.jpg
similarity index 100%
rename from native/graphics/jni/corpus/baseline_jpeg.jpg
rename to native/graphics/jni/fuzz/corpus/baseline_jpeg.jpg
Binary files differ
diff --git a/native/graphics/jni/corpus/color_wheel.ico b/native/graphics/jni/fuzz/corpus/color_wheel.ico
similarity index 100%
rename from native/graphics/jni/corpus/color_wheel.ico
rename to native/graphics/jni/fuzz/corpus/color_wheel.ico
Binary files differ
diff --git a/native/graphics/jni/corpus/gif_test.gif b/native/graphics/jni/fuzz/corpus/gif_test.gif
similarity index 100%
rename from native/graphics/jni/corpus/gif_test.gif
rename to native/graphics/jni/fuzz/corpus/gif_test.gif
Binary files differ
diff --git a/native/graphics/jni/corpus/google_chrome.ico b/native/graphics/jni/fuzz/corpus/google_chrome.ico
similarity index 100%
rename from native/graphics/jni/corpus/google_chrome.ico
rename to native/graphics/jni/fuzz/corpus/google_chrome.ico
Binary files differ
diff --git a/native/graphics/jni/corpus/heifwriter_input.heic b/native/graphics/jni/fuzz/corpus/heifwriter_input.heic
similarity index 100%
rename from native/graphics/jni/corpus/heifwriter_input.heic
rename to native/graphics/jni/fuzz/corpus/heifwriter_input.heic
Binary files differ
diff --git a/native/graphics/jni/corpus/mandrill.wbmp b/native/graphics/jni/fuzz/corpus/mandrill.wbmp
similarity index 100%
rename from native/graphics/jni/corpus/mandrill.wbmp
rename to native/graphics/jni/fuzz/corpus/mandrill.wbmp
Binary files differ
diff --git a/native/graphics/jni/corpus/progressive_jpeg.jpg b/native/graphics/jni/fuzz/corpus/progressive_jpeg.jpg
similarity index 100%
rename from native/graphics/jni/corpus/progressive_jpeg.jpg
rename to native/graphics/jni/fuzz/corpus/progressive_jpeg.jpg
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_1mp.dng b/native/graphics/jni/fuzz/corpus/sample_1mp.dng
similarity index 100%
rename from native/graphics/jni/corpus/sample_1mp.dng
rename to native/graphics/jni/fuzz/corpus/sample_1mp.dng
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_arw.arw b/native/graphics/jni/fuzz/corpus/sample_arw.arw
similarity index 100%
rename from native/graphics/jni/corpus/sample_arw.arw
rename to native/graphics/jni/fuzz/corpus/sample_arw.arw
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_cr2.cr2 b/native/graphics/jni/fuzz/corpus/sample_cr2.cr2
similarity index 100%
rename from native/graphics/jni/corpus/sample_cr2.cr2
rename to native/graphics/jni/fuzz/corpus/sample_cr2.cr2
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_nef.nef b/native/graphics/jni/fuzz/corpus/sample_nef.nef
similarity index 100%
rename from native/graphics/jni/corpus/sample_nef.nef
rename to native/graphics/jni/fuzz/corpus/sample_nef.nef
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_nrw.nrw b/native/graphics/jni/fuzz/corpus/sample_nrw.nrw
similarity index 100%
rename from native/graphics/jni/corpus/sample_nrw.nrw
rename to native/graphics/jni/fuzz/corpus/sample_nrw.nrw
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_orf.orf b/native/graphics/jni/fuzz/corpus/sample_orf.orf
similarity index 100%
rename from native/graphics/jni/corpus/sample_orf.orf
rename to native/graphics/jni/fuzz/corpus/sample_orf.orf
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_pef.pef b/native/graphics/jni/fuzz/corpus/sample_pef.pef
similarity index 100%
rename from native/graphics/jni/corpus/sample_pef.pef
rename to native/graphics/jni/fuzz/corpus/sample_pef.pef
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_raf.raf b/native/graphics/jni/fuzz/corpus/sample_raf.raf
similarity index 100%
rename from native/graphics/jni/corpus/sample_raf.raf
rename to native/graphics/jni/fuzz/corpus/sample_raf.raf
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_rw2.rw2 b/native/graphics/jni/fuzz/corpus/sample_rw2.rw2
similarity index 100%
rename from native/graphics/jni/corpus/sample_rw2.rw2
rename to native/graphics/jni/fuzz/corpus/sample_rw2.rw2
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_srw.srw b/native/graphics/jni/fuzz/corpus/sample_srw.srw
similarity index 100%
rename from native/graphics/jni/corpus/sample_srw.srw
rename to native/graphics/jni/fuzz/corpus/sample_srw.srw
Binary files differ
diff --git a/native/graphics/jni/corpus/webp-color-profile-lossless.webp b/native/graphics/jni/fuzz/corpus/webp-color-profile-lossless.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp-color-profile-lossless.webp
rename to native/graphics/jni/fuzz/corpus/webp-color-profile-lossless.webp
Binary files differ
diff --git a/native/graphics/jni/corpus/webp_animated.webp b/native/graphics/jni/fuzz/corpus/webp_animated.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp_animated.webp
rename to native/graphics/jni/fuzz/corpus/webp_animated.webp
Binary files differ
diff --git a/native/graphics/jni/corpus/webp_test.webp b/native/graphics/jni/fuzz/corpus/webp_test.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp_test.webp
rename to native/graphics/jni/fuzz/corpus/webp_test.webp
Binary files differ
diff --git a/native/graphics/jni/fuzz_imagedecoder.cpp b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
similarity index 96%
rename from native/graphics/jni/fuzz_imagedecoder.cpp
rename to native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
index 015aca7..838bf3f 100644
--- a/native/graphics/jni/fuzz_imagedecoder.cpp
+++ b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
@@ -22,6 +22,10 @@
#include <cstdlib>
#include <memory>
+#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
+#include <fuzz/png_mutator.h>
+#endif
+
struct DecoderDeleter {
void operator()(AImageDecoder* decoder) const { AImageDecoder_delete(decoder); }
};
diff --git a/native/graphics/jni/fuzz/png_mutator.h b/native/graphics/jni/fuzz/png_mutator.h
new file mode 100644
index 0000000..f23c343
--- /dev/null
+++ b/native/graphics/jni/fuzz/png_mutator.h
@@ -0,0 +1,326 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <cstring>
+#include <cassert>
+#include <random>
+
+#include <zlib.h>
+
+// A simple class for parsing, serializing, and mutating an PNG file.
+// https://en.wikipedia.org/wiki/Portable_Network_Graphics
+// It is an example of a custom mutator for libFuzzer
+// (https://llvm.org/docs/LibFuzzer.html) used for
+// "structure-aware coverage-guided fuzzing".
+//
+// If you have a non structure-aware fuzz target for any API that handles
+// PNG inputs, you can turn that fuzz target into a structure-aware one
+// by defining PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR and then
+// including this file.
+class PngMutator {
+ using V = std::vector<uint8_t>;
+
+ public:
+
+ // Parse the input stream as a PNG file,
+ // put every chunk into its own vector,
+ // uncompress chunk data when needed,
+ // merge the IDAT chunks into one vector.
+ PngMutator(std::istream &in) {
+ ihdr_.resize(13);
+ Read4(in);
+ Read4(in); // Skip the 8-byte magic value.
+ // read IHDR.
+ if (ReadInteger(in) != 13) return;
+ if (Read4(in) != Type("IHDR")) return;
+ // Read 13 values.
+ in.read((char*)ihdr_.data(), ihdr_.size());
+ Read4(in); // ignore CRC
+ ssize_t idat_idx = -1;
+
+ while (in) {
+ uint32_t len = ReadInteger(in);
+ uint32_t type = Read4(in);
+ if (type == Type("IEND")) break; // do nothing
+ char chunk_name[5];
+ memcpy(chunk_name, &type, 4);
+ chunk_name[4] = 0;
+ if (len > (1 << 20)) return;
+ V v(len);
+ in.read((char *)v.data(), len);
+ Read4(in); // ignore CRC
+
+ if (type == Type("IDAT")) {
+ if (idat_idx != -1)
+ Append(&chunks_[idat_idx].v, v);
+ else {
+ idat_idx = chunks_.size();
+ chunks_.push_back({type, v});
+ }
+ } else if (type == Type("iCCP")) {
+ auto it = v.begin();
+ while (it < v.end() && isprint(*it)) it++;
+ if (it < v.end() && !*it) it++;
+ if (it < v.end() && !*it) it++;
+ v = V(it, v.end());
+ auto uncompressed = Uncompress(v);
+ chunks_.push_back({type, uncompressed});
+ auto compressed = Compress(uncompressed);
+ } else {
+ chunks_.push_back({type, v});
+ }
+ // std::cerr << "CHUNK: " << chunk_name << std::endl;
+ }
+ if (idat_idx != -1)
+ chunks_[idat_idx].v = Uncompress(chunks_[idat_idx].v);
+ }
+
+ // Write back the PNG file.
+ void Serialize(std::ostream &out) {
+ const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47,
+ 0x0d, 0x0a, 0x1a, 0x0a};
+ out.write((const char*)header, sizeof(header));
+ WriteChunk(out, "IHDR", ihdr_);
+ for (auto &ch : chunks_) {
+ if (ch.type == Type("iCCP")) {
+ V v;
+ v.push_back('x'); // assuming the iCCP name doesn't matter.
+ v.push_back(0);
+ v.push_back(0);
+ auto compressed = Compress(ch.v);
+ Append(&v, compressed);
+ WriteChunk(out, ch.type, v);
+ } else {
+ WriteChunk(out, ch.type, ch.v);
+ }
+ }
+
+ WriteChunk(out, "IEND", {});
+ }
+
+ // Raw byte array mutator, like that provided by libFuzzer.
+ using Mutator = size_t (*)(uint8_t *Data, size_t Size, size_t MaxSize);
+
+ // Mutate the in-memory representation of a PNG file.
+ // Given the same Seed, the same mutation is performed.
+ void Mutate(Mutator m, unsigned int Seed) {
+ std::minstd_rand rnd(Seed);
+ auto M = [&](V *v) {
+ if (v->empty())
+ v->resize(v->size() + 1 + rnd() % 256);
+ v->resize(m(v->data(), v->size(), v->size()));
+ };
+ switch (rnd() % 6) {
+ // Mutate IHDR.
+ case 0:
+ m(ihdr_.data(), ihdr_.size(), ihdr_.size());
+ break;
+ // Mutate some other chunk.
+ case 1:
+ if (!chunks_.empty()) M(&chunks_[rnd() % chunks_.size()].v);
+ break;
+ // Shuffle the chunks.
+ case 2:
+ std::shuffle(chunks_.begin(), chunks_.end(), rnd);
+ break;
+ // Delete a random chunk.
+ case 3:
+ if (!chunks_.empty())
+ chunks_.erase(chunks_.begin() + rnd() % chunks_.size());
+ break;
+ // Insert a random chunk with one of the known types, or a random type.
+ case 4: {
+ static const char *types[] = {
+ "IATx", "sTER", "hIST", "sPLT", "mkBF", "mkBS", "mkTS", "prVW",
+ "oFFs", "iDOT", "zTXt", "mkBT", "acTL", "iTXt", "sBIT", "tIME",
+ "iCCP", "vpAg", "tRNS", "cHRM", "PLTE", "bKGD", "gAMA", "sRGB",
+ "pHYs", "fdAT", "fcTL", "tEXt", "IDAT",
+ "pCAL", "sCAL", "eXIf",
+ "fUZz", // special chunk for extra fuzzing hints.
+ };
+ static const size_t n_types = sizeof(types) / sizeof(types[0]);
+ uint32_t type =
+ (rnd() % 10 <= 8) ? Type(types[rnd() % n_types]) : (uint32_t)rnd();
+ size_t len = rnd() % 256;
+ if (type == Type("fUZz"))
+ len = 16;
+ V v(len);
+ for (auto &b : v) b = rnd();
+ size_t pos = rnd() % (chunks_.size() + 1);
+ chunks_.insert(chunks_.begin() + pos, {type, v});
+ } break;
+ // Any more interesting mutations with a PNG file?
+ case 5: {
+ auto it = std::find_if(
+ chunks_.begin(), chunks_.end(),
+ [](const Chunk &ch) { return ch.type == Type("fUZz"); });
+ if (it != chunks_.end())
+ m(it->v.data(), it->v.size(), it->v.size());
+ }
+
+ }
+ }
+
+ // Takes a random chunk from p and inserts into *this.
+ void CrossOver(const PngMutator &p, unsigned int Seed) {
+ if (p.chunks_.empty()) return;
+ std::minstd_rand rnd(Seed);
+ size_t idx = rnd() % p.chunks_.size();
+ auto &ch = p.chunks_[idx];
+ size_t pos = rnd() % (chunks_.size() + 1);
+ chunks_.insert(chunks_.begin() + pos, ch);
+ }
+
+ private:
+ void Append(V *to, const V &from) {
+ to->insert(to->end(), from.begin(), from.end());
+ }
+
+ uint32_t Read4(std::istream &in) {
+ uint32_t res = 0;
+ in.read((char *)&res, sizeof(res));
+ return res;
+ }
+ uint32_t ReadInteger(std::istream &in) {
+ return __builtin_bswap32(Read4(in));
+ }
+ static uint32_t Type(const char *tagname) {
+ uint32_t res;
+ assert(strlen(tagname) == 4);
+ memcpy(&res, tagname, 4);
+ return res;
+ }
+
+ void WriteInt(std::ostream &out, uint32_t x) {
+ x = __builtin_bswap32(x);
+ out.write((char *)&x, sizeof(x));
+ }
+
+ // Chunk is written as:
+ // * 4-byte length
+ // * 4-byte type
+ // * the data itself
+ // * 4-byte crc (of type and data)
+ void WriteChunk(std::ostream &out, const char *type, const V &chunk,
+ bool compress = false) {
+ V compressed;
+ const V *v = &chunk;
+ if (compress) {
+ compressed = Compress(chunk);
+ v = &compressed;
+ }
+ uint32_t len = v->size();
+ uint32_t crc = crc32(0, (const unsigned char *)type, 4);
+ if (v->size())
+ crc = crc32(crc, (const unsigned char *)v->data(), v->size());
+ WriteInt(out, len);
+ out.write(type, 4);
+ out.write((const char*)v->data(), v->size());
+ WriteInt(out, crc);
+ }
+
+ void WriteChunk(std::ostream &out, uint32_t type, const V &chunk) {
+ char type_s[5];
+ memcpy(type_s, &type, 4);
+ type_s[4] = 0;
+ WriteChunk(out, type_s, chunk);
+ }
+
+ V Uncompress(const V &compressed) {
+ V v;
+ static const size_t kMaxBuffer = 1 << 28;
+ for (size_t sz = compressed.size() * 4; sz < kMaxBuffer; sz *= 2) {
+ v.resize(sz);
+ unsigned long len = sz;
+ auto res =
+ uncompress(v.data(), &len, compressed.data(), compressed.size());
+ if (res == Z_BUF_ERROR) continue;
+ if (res != Z_OK) return {};
+ v.resize(len);
+ break;
+ }
+ return v;
+ }
+
+ V Compress(const V &uncompressed) {
+ V v;
+ static const size_t kMaxBuffer = 1 << 28;
+ for (size_t sz = uncompressed.size(); sz < kMaxBuffer; sz *= 2) {
+ v.resize(sz);
+ unsigned long len = sz;
+ auto res =
+ compress(v.data(), &len, uncompressed.data(), uncompressed.size());
+ if (res == Z_BUF_ERROR) continue;
+ if (res != Z_OK) return {};
+ v.resize(len);
+ break;
+ }
+ return v;
+ }
+
+ void PrintHex(const V &v, size_t max_n) {
+ for (size_t i = 0; i < max_n && i < v.size(); i++) {
+ std::cerr << "0x" << std::hex << (unsigned)v[i] << " " << std::dec;
+ }
+ std::cerr << std::endl;
+ }
+
+ V ihdr_;
+
+ struct Chunk {
+ uint32_t type;
+ V v;
+ };
+ std::vector<Chunk> chunks_;
+};
+
+
+#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
+
+extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+#if STANDALONE_TARGET
+size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) {
+ assert(false && "LLVMFuzzerMutate should not be called from StandaloneFuzzTargetMain");
+ return 0;
+}
+#endif
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+ size_t MaxSize, unsigned int Seed) {
+ std::string s(reinterpret_cast<const char*>(Data), Size);
+ std::stringstream in(s);
+ std::stringstream out;
+ PngMutator p(in);
+ p.Mutate(LLVMFuzzerMutate, Seed);
+ p.Serialize(out);
+ const auto &str = out.str();
+ if (str.size() > MaxSize) return Size;
+ memcpy(Data, str.data(), str.size());
+ return str.size();
+}
+
+extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+ const uint8_t *Data2, size_t Size2,
+ uint8_t *Out, size_t MaxOutSize,
+ unsigned int Seed) {
+ std::stringstream in1(
+ std::string(reinterpret_cast<const char *>(Data1), Size1));
+ std::stringstream in2(
+ std::string(reinterpret_cast<const char *>(Data2), Size2));
+ PngMutator p1(in1);
+ PngMutator p2(in2);
+ p1.CrossOver(p2, Seed);
+ std::stringstream out;
+ p1.Serialize(out);
+ const auto &str = out.str();
+ if (str.size() > MaxOutSize) return 0;
+ memcpy(Out, str.data(), str.size());
+ return str.size();
+}
+
+#endif // PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt b/packages/EasterEgg/src/com/android/egg/neko/NekoControlsService.kt
index 56f599a..7efaf0b 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 @@
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 @@
return getPendingIntent()
}
-
override fun performControlAction(
controlId: String,
action: ControlAction,
@@ -196,7 +196,7 @@
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 460fa3a..20527af 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 @@
) : 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 ce439a9..578de01 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 @@
private lateinit var label: Button
private lateinit var grid: GridLayout
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml
index 12dc170..05561d7 100644
--- a/packages/FusedLocation/AndroidManifest.xml
+++ b/packages/FusedLocation/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<application
android:label="@string/app_label"
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index cb55c72..7a239af 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -273,6 +273,7 @@
.setLowPower(mRequest.isLowPower())
.setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored())
.setWorkSource(mRequest.getWorkSource())
+ .setHiddenFromAppOps(true)
.build();
mLocationManager.requestLocationUpdates(mProvider, request,
mContext.getMainExecutor(), this);
diff --git a/packages/FusedLocation/test/AndroidManifest.xml b/packages/FusedLocation/test/AndroidManifest.xml
index c00d15e..c38cecb 100644
--- a/packages/FusedLocation/test/AndroidManifest.xml
+++ b/packages/FusedLocation/test/AndroidManifest.xml
@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<application android:label="FusedLocation Tests">
<uses-library android:name="android.test.runner" />
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 3105e98..711a22f 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -576,8 +576,7 @@
<string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string>
<string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
<string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
- <!-- no translation found for data_connection_carrier_wifi (2250268321065848954) -->
- <skip />
+ <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
<string name="cell_data_off_content_description" msgid="2280700839891636498">"モバイルデータ OFF"</string>
<string name="not_default_data_content_description" msgid="6517068332106592887">"データを使用するように設定されていません"</string>
<string name="accessibility_no_phone" msgid="2687419663127582503">"電波状態:なし"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index f5be76e..72ddc8f 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -576,8 +576,7 @@
<string name="data_connection_4g_plus" msgid="5194902328408751020">"४G+"</string>
<string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
<string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
- <!-- no translation found for data_connection_carrier_wifi (2250268321065848954) -->
- <skip />
+ <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
<string name="cell_data_off_content_description" msgid="2280700839891636498">"मोबाइल डेटा बंद आहे"</string>
<string name="not_default_data_content_description" msgid="6517068332106592887">"डेटा वापरण्यासाठी सेट केलेले नाही"</string>
<string name="accessibility_no_phone" msgid="2687419663127582503">"कोणताही फोन नाही."</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 2ae7d71..e04e201 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -576,8 +576,7 @@
<string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string>
<string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
<string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
- <!-- no translation found for data_connection_carrier_wifi (2250268321065848954) -->
- <skip />
+ <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
<string name="cell_data_off_content_description" msgid="2280700839891636498">"ਮੋਬਾਈਲ ਡਾਟਾ ਬੰਦ"</string>
<string name="not_default_data_content_description" msgid="6517068332106592887">"ਡਾਟਾ ਵਰਤਣ ਲਈ ਸੈੱਟ ਨਹੀਂ"</string>
<string name="accessibility_no_phone" msgid="2687419663127582503">"ਕੋਈ ਫ਼ੋਨ ਨਹੀਂ।"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 897abea..5e880a9 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -140,15 +140,15 @@
<string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ఓపెన్ నెట్వర్క్"</string>
<string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"సురక్షిత నెట్వర్క్"</string>
<string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
- <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"తీసివేయబడిన అనువర్తనాలు"</string>
- <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"తీసివేయబడిన అనువర్తనాలు మరియు వినియోగదారులు"</string>
+ <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"తీసివేయబడిన యాప్లు"</string>
+ <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"తీసివేయబడిన యాప్లు మరియు వినియోగదారులు"</string>
<string name="data_usage_ota" msgid="7984667793701597001">"సిస్టమ్ అప్డేట్లు"</string>
<string name="tether_settings_title_usb" msgid="3728686573430917722">"USB టీథరింగ్"</string>
<string name="tether_settings_title_wifi" msgid="4803402057533895526">"పోర్టబుల్ హాట్స్పాట్"</string>
<string name="tether_settings_title_bluetooth" msgid="916519902721399656">"బ్లూటూత్ టెథెరింగ్"</string>
<string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"టీథరింగ్"</string>
<string name="tether_settings_title_all" msgid="8910259483383010470">"టీథరింగ్ & పోర్టబుల్ హాట్స్పాట్"</string>
- <string name="managed_user_title" msgid="449081789742645723">"అన్ని కార్యాలయ అనువర్తనాలు"</string>
+ <string name="managed_user_title" msgid="449081789742645723">"అన్ని కార్యాలయ యాప్లు"</string>
<string name="user_guest" msgid="6939192779649870792">"అతిథి"</string>
<string name="unknown" msgid="3544487229740637809">"తెలియదు"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"వినియోగదారు: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
@@ -313,14 +313,14 @@
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"బ్లూటూత్ Gabeldorsche ఫీచర్ స్ట్యాక్ను ఎనేబుల్ చేస్తుంది."</string>
<string name="enhanced_connectivity_summary" msgid="1576414159820676330">"మెరుగైన కనెక్టివిటీ ఫీచర్ను ఎనేబుల్ చేస్తుంది."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"స్థానిక టెర్మినల్"</string>
- <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ ప్రాప్యతను అందించే టెర్మినల్ అనువర్తనాన్ని ప్రారంభించు"</string>
+ <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ ప్రాప్యతను అందించే టెర్మినల్ యాప్ను ప్రారంభించు"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP తనిఖీ"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP తనిఖీ ప్రవర్తనను సెట్ చేయండి"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"డీబగ్గింగ్"</string>
<string name="debug_app" msgid="8903350241392391766">"డీబగ్ యాప్ను ఎంచుకోండి"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"డీబగ్ యాప్ సెట్ చేయబడలేదు"</string>
<string name="debug_app_set" msgid="6599535090477753651">"డీబగ్గింగ్ యాప్: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="select_application" msgid="2543228890535466325">"అనువర్తనాన్ని ఎంచుకోండి"</string>
+ <string name="select_application" msgid="2543228890535466325">"యాప్ను ఎంచుకోండి"</string>
<string name="no_application" msgid="9038334538870247690">"ఏదీ వద్దు"</string>
<string name="wait_for_debugger" msgid="7461199843335409809">"డీబగ్గర్ కోసం వేచి ఉండండి"</string>
<string name="wait_for_debugger_summary" msgid="6846330006113363286">"డీబగ్ చేయబడిన యాప్ అమలు కావడానికి ముందు జోడించాల్సిన డీబగ్గర్ కోసం వేచి ఉంటుంది"</string>
@@ -547,7 +547,7 @@
<string name="user_new_profile_name" msgid="2405500423304678841">"కొత్త ప్రొఫైల్"</string>
<string name="user_info_settings_title" msgid="6351390762733279907">"వినియోగదారు సమాచారం"</string>
<string name="profile_info_settings_title" msgid="105699672534365099">"ప్రొఫైల్ సమాచారం"</string>
- <string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్ను సృష్టించడానికి ముందు, మీ అనువర్తనాలు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్ను సెటప్ చేయాల్సి ఉంటుంది."</string>
+ <string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్ను సృష్టించడానికి ముందు, మీ యాప్లు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్ను సెటప్ చేయాల్సి ఉంటుంది."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"లాక్ను సెట్ చేయి"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు మార్చు"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index ac20ee1..927da0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -267,19 +267,28 @@
permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
packageName, userId);
}
+
+ boolean permittedByParentAdmin = true;
+ EnforcedAdmin profileAdmin = null;
int managedProfileId = getManagedProfileId(context, userId);
- EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context,
- getUserHandleOf(managedProfileId));
- boolean permittedByProfileAdmin = true;
- if (profileAdmin != null) {
- permittedByProfileAdmin = dpm.isInputMethodPermittedByAdmin(profileAdmin.component,
- packageName, managedProfileId);
+ if (managedProfileId != UserHandle.USER_NULL) {
+ profileAdmin = getProfileOrDeviceOwner(context, getUserHandleOf(managedProfileId));
+ // If the device is an organization-owned device with a managed profile, the
+ // managedProfileId will be used instead of the affected userId. This is because
+ // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
+ // return results affecting the personal profile.
+ if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
+ DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm,
+ UserManager.get(context).getUserInfo(managedProfileId));
+ permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
+ profileAdmin.component, packageName, managedProfileId);
+ }
}
- if (!permitted && !permittedByProfileAdmin) {
+ if (!permitted && !permittedByParentAdmin) {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
} else if (!permitted) {
return admin;
- } else if (!permittedByProfileAdmin) {
+ } else if (!permittedByParentAdmin) {
return profileAdmin;
}
return null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt
index b2e75ea..cd22247 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 @@
* @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 @@
* @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 @@
* 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 859f5a6..1c53418 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -388,6 +388,9 @@
<!-- Permission required for CTS to test sensor privacy behavior -->
<uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" />
+ <!-- Permission needed for CTS test - CallLogTest -->
+ <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 39fbb34..ca9dcd6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -354,14 +354,6 @@
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
- <activity
- android:name="com.android.wm.shell.bubbles.BubbleOverflowActivity"
- android:theme="@style/BubbleOverflow"
- android:excludeFromRecents="true"
- android:documentLaunchMode="always"
- android:resizeableActivity="true">
- </activity>
-
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
android:icon="@drawable/tuner"
@@ -441,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/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 0c8c5c4..dec83d9 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -43,6 +43,7 @@
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.toast.ToastUI</item>
<item>com.android.systemui.wmshell.WMShell</item>
+ <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item>
</string-array>
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ac2e342..6e9c5dc 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 6c0635a..d706ec2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -15,8 +15,6 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="BubbleOverflow" parent="@android:style/Theme.NoTitleBar"></style>
-
<style name="ClearAllButtonDefaultMargins">
<item name="android:layout_marginStart">0dp</item>
<item name="android:layout_marginTop">0dp</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 57a4dab..388eeb6 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.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/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 77e568c..a40dc7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -255,6 +255,7 @@
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+ private final Context mContext;
private KeyguardClockSwitchController mKeyguardClockSwitchController;
private View mClock;
private int mUsableWidth;
@@ -278,6 +279,7 @@
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
setCancelable(false);
+ mContext = context;
}
@Override
@@ -301,7 +303,7 @@
mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
- setContentView(LayoutInflater.from(getContext())
+ setContentView(LayoutInflater.from(mContext)
.inflate(R.layout.keyguard_presentation, null));
// Logic to make the lock screen fullscreen
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b43496c..e59769b 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.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 @@
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 @@
} 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 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 @@
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 @@
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 @@
}
/**
+ * 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 36617c2..e561a5a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -328,4 +328,9 @@
*/
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/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 9f6c19b..55359ea 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -27,6 +27,7 @@
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.media.systemsounds.HomeSoundEffectController;
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
@@ -178,4 +179,10 @@
@IntoMap
@ClassKey(WMShell.class)
public abstract SystemUI bindWMShell(WMShell sysui);
+
+ /** Inject into HomeSoundEffectController. */
+ @Binds
+ @IntoMap
+ @ClassKey(HomeSoundEffectController.class)
+ public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
new file mode 100644
index 0000000..dd3d02a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.systemsounds;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.media.AudioManager;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+
+import javax.inject.Inject;
+
+/**
+ * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a sound is played
+ * when the home task moves to front and the last task that moved to front was not the home task.
+ */
+@SysUISingleton
+public class HomeSoundEffectController extends SystemUI {
+
+ private final AudioManager mAudioManager;
+ private final TaskStackChangeListeners mTaskStackChangeListeners;
+ // Initialize true because home sound should not be played when the system boots.
+ private boolean mIsLastTaskHome = true;
+
+ @Inject
+ public HomeSoundEffectController(
+ Context context,
+ AudioManager audioManager,
+ TaskStackChangeListeners taskStackChangeListeners) {
+ super(context);
+ mAudioManager = audioManager;
+ mTaskStackChangeListeners = taskStackChangeListeners;
+ }
+
+ @Override
+ public void start() {
+ if (mAudioManager.isHomeSoundEffectEnabled()) {
+ mTaskStackChangeListeners.registerTaskStackListener(
+ new TaskStackChangeListener() {
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ handleHomeTaskMovedToFront(taskInfo);
+ }
+ });
+ }
+ }
+
+ private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ }
+
+ /**
+ * To enable a home sound, check if the home app moves to front.
+ */
+ private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ boolean isCurrentTaskHome = isHomeTask(taskInfo);
+ // If the last task is home we don't want to play the home sound. This avoids playing
+ // the home sound after FallbackHome transitions to Home
+ if (!mIsLastTaskHome && isCurrentTaskHome) {
+ mAudioManager.playSoundEffect(AudioManager.FX_HOME);
+ }
+ mIsLastTaskHome = isCurrentTaskHome;
+ }
+}
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 98740a2..b6e07b1 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 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 @@
@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 8cc0d7b..9cc6f09 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 @@
@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/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
new file mode 100644
index 0000000..c1161f1
--- /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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5259aa9..c816784 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 @@
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/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index b5963ef..a12179c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -78,6 +78,7 @@
private ExpandableNotificationRow mTrackedHeadsUpRow;
private float mAppearFraction;
private boolean mIsShadeOpening;
+ private float mSectionPadding;
/** Tracks the state from AlertingNotificationManager#hasNotifications() */
private boolean mHasAlertEntries;
@@ -105,6 +106,14 @@
return mIsShadeOpening;
}
+ void setSectionPadding(float padding) {
+ mSectionPadding = padding;
+ }
+
+ float getSectionPadding() {
+ return mSectionPadding;
+ }
+
private static int getZDistanceBetweenElements(Context context) {
return Math.max(1, context.getResources()
.getDimensionPixelSize(R.dimen.z_distance_between_notifications));
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 6abbc6b..f07d874 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
@@ -554,6 +554,11 @@
mAmbientState.setIsShadeOpening(isOpening);
}
+ void setSectionPadding(float margin) {
+ mAmbientState.setSectionPadding(margin);
+ requestChildrenUpdate();
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onFinishInflate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5126f55..879dad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -275,6 +275,10 @@
mView.setIsShadeOpening(isOpening);
}
+ public void setSectionPadding(float padding) {
+ mView.setSectionPadding(padding);
+ }
+
private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
@Override
public void onMenuClicked(
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 2c206b1..e6efba7 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
@@ -341,7 +341,8 @@
boolean isEmptyShadeView = child instanceof EmptyShadeView;
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
- float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
+ float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
+ + ambientState.getSectionPadding();
if (i <= algorithmState.getIndexOfExpandingNotification()) {
inset += ambientState.getExpandAnimationTopChange();
}
@@ -563,6 +564,10 @@
childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
}
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
+ if (child instanceof SectionHeaderView) {
+ // Add padding before sections for overscroll effect.
+ childViewState.yTranslation += ambientState.getSectionPadding();
+ }
if (childViewState.yTranslation >= shelfStart) {
childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
childViewState.inShelf = true;
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 6cd7a74..1dfd1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -32,7 +32,6 @@
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
*/
public class KeyguardClockPositionAlgorithm {
-
/**
* How much the clock height influences the shade position.
* 0 means nothing, 1 means move the shade up by the height of the clock
@@ -81,6 +80,11 @@
private int mMinTopMargin;
/**
+ * Minimum top inset (in pixels) to avoid overlap with any display cutouts.
+ */
+ private int mCutoutTopInset = 0;
+
+ /**
* Maximum bottom padding to avoid overlap with {@link KeyguardBottomAreaView} or
* the ambient indication.
*/
@@ -172,7 +176,7 @@
float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
- float qsExpansion) {
+ float qsExpansion, int cutoutTopInset) {
mMinTopMargin = statusBarMinHeight + (showLockIcon
? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon);
mMaxShadeBottom = maxShadeBottom;
@@ -188,6 +192,7 @@
mBypassEnabled = bypassEnabled;
mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
mQsExpansion = qsExpansion;
+ mCutoutTopInset = cutoutTopInset;
}
public void run(Result result) {
@@ -270,10 +275,13 @@
float darkAmount = mBypassEnabled && !mHasCustomClock ? 1.0f : mDarkAmount;
- // TODO(b/12836565) - prototyping only adjustment
if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
- // This will keep the clock at the top
- clockYDark = (int) (clockY + burnInPreventionOffsetY());
+ // This will keep the clock at the top but out of the cutout area
+ float shift = 0;
+ if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
+ shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
+ }
+ clockYDark = clockY + burnInPreventionOffsetY() + shift;
}
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
}
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 d132abe..3e09784 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -50,6 +50,7 @@
import android.os.SystemClock;
import android.util.Log;
import android.util.MathUtils;
+import android.view.DisplayCutout;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -344,6 +345,7 @@
private float mEmptyDragAmount;
private float mDownX;
private float mDownY;
+ private int mDisplayCutoutTopInset = 0; // in pixels
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
@@ -475,6 +477,7 @@
private boolean mShowingKeyguardHeadsUp;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
+ private float mSectionPadding;
/**
* Is this a collapse that started on the panel where we should allow the panel to intercept
@@ -909,7 +912,8 @@
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
mUpdateMonitor.shouldShowLockIcon(),
- getQsExpansionFraction());
+ getQsExpansionFraction(),
+ mDisplayCutoutTopInset);
mClockPositionAlgorithm.run(mClockPositionResult);
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
@@ -2413,6 +2417,16 @@
}
@Override
+ public void setSectionPadding(float padding) {
+ if (padding == mSectionPadding) {
+ return;
+ }
+ mSectionPadding = padding;
+ mQsFrame.setTranslationY(padding);
+ mNotificationStackScrollLayoutController.setSectionPadding(padding);
+ }
+
+ @Override
protected void setOverExpansion(float overExpansion, boolean isPixels) {
if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
return;
@@ -2499,19 +2513,6 @@
}
@Override
- protected boolean shouldExpandToTopOfClearAll(float targetHeight) {
- boolean perform = super.shouldExpandToTopOfClearAll(targetHeight);
- if (!perform) {
- return false;
- }
- // Let's make sure we're not appearing but the animation will end below the appear.
- // Otherwise quick settings would jump at the end of the animation.
- float fraction = mNotificationStackScrollLayoutController
- .calculateAppearFraction(targetHeight);
- return fraction >= 1.0f;
- }
-
- @Override
protected boolean shouldUseDismissingAnimation() {
return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
|| !isTracking());
@@ -2530,11 +2531,6 @@
}
@Override
- protected int getClearAllHeightWithPadding() {
- return mNotificationStackScrollLayoutController.getFooterViewHeightWithPadding();
- }
-
- @Override
protected boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3835,6 +3831,9 @@
private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ final DisplayCutout displayCutout = v.getRootWindowInsets().getDisplayCutout();
+ mDisplayCutoutTopInset = displayCutout != null ? displayCutout.getSafeInsetTop() : 0;
+
mNavigationBarBottomHeight = insets.getStableInsetBottom();
updateMaxHeadsUpTranslation();
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index a1112dc..bee0cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -57,6 +57,10 @@
}
}
+ protected boolean isShadeOpening() {
+ return mState == STATE_OPENING;
+ }
+
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index da82986..3031b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -32,6 +32,7 @@
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.util.Log;
+import android.util.MathUtils;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -67,6 +68,13 @@
private static final int INITIAL_OPENING_PEEK_DURATION = 200;
private static final int PEEK_ANIMATION_DURATION = 360;
private static final int NO_FIXED_DURATION = -1;
+ private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
+ private static final long SHADE_OPEN_SPRING_BACK_DURATION = 200L;
+ private static final float MIN_OVERSCROLL = -50;
+ private static final float MAX_OVERSCROLL = 30;
+
+ private float mFlingTarget;
+ private float mFlingVelocity;
protected long mDownTime;
protected boolean mTouchSlopExceededBeforeDown;
private float mMinExpandHeight;
@@ -569,14 +577,6 @@
protected void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- // Hack to make the expand transition look nice when clear all button is visible - we make
- // the animation only to the last notification, and then jump to the maximum panel height so
- // clear all just fades in and the decelerating motion is towards the last notification.
- final boolean clearAllExpandHack = expand &&
- shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeightWithPadding());
- if (clearAllExpandHack) {
- target = getMaxPanelHeight() - getClearAllHeightWithPadding();
- }
if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
notifyExpandingFinished();
@@ -584,13 +584,14 @@
}
mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
ValueAnimator animator = createHeightAnimator(target);
+ mFlingTarget = target;
if (expand) {
if (expandBecauseOfFalsing && vel < 0) {
vel = 0;
}
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight());
if (vel == 0) {
- animator.setDuration(350);
+ animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
}
} else {
if (shouldUseDismissingAnimation()) {
@@ -615,6 +616,7 @@
animator.setDuration(mFixedDuration);
}
}
+ mFlingVelocity = vel;
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -625,38 +627,54 @@
@Override
public void onAnimationEnd(Animator animation) {
- if (clearAllExpandHack && !mCancelled) {
- setExpandedHeightInternal(getMaxPanelHeight());
- }
- setAnimator(null);
- if (!mCancelled) {
- InteractionJankMonitor.getInstance()
- .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- notifyExpandingFinished();
+ if (expand && mFlingVelocity > 0) {
+ // After the shade is flinged open to an overscrolled state, spring back
+ // the shade by reducing section padding to 0.
+ springBack();
} else {
- InteractionJankMonitor.getInstance()
- .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ onFlingEnd(mCancelled);
}
- notifyBarPanelExpansionChanged();
}
});
setAnimator(animator);
animator.start();
}
- /**
- * When expanding, should we expand to the top of clear all and expand immediately?
- * This will make sure that the animation will stop smoothly at the end of the last notification
- * before the clear all affordance.
- *
- * @param targetHeight the height that we would animate to, right above clear all
- *
- * @return true if we can expand to the top of clear all
- */
- protected boolean shouldExpandToTopOfClearAll(float targetHeight) {
- return fullyExpandedClearAllVisible()
- && mExpandedHeight < targetHeight
- && !isClearAllVisible();
+ private void springBack() {
+ ValueAnimator animator = ValueAnimator.ofFloat(MAX_OVERSCROLL, 0);
+ animator.addUpdateListener(
+ animation -> {
+ setSectionPadding((float) animation.getAnimatedValue());
+ setExpandedHeightInternal(mFlingTarget);
+ });
+ animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onFlingEnd(mCancelled);
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ private void onFlingEnd(boolean cancelled) {
+ setAnimator(null);
+ if (!cancelled) {
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ notifyExpandingFinished();
+ } else {
+ InteractionJankMonitor.getInstance()
+ .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+ notifyBarPanelExpansionChanged();
}
protected abstract boolean shouldUseDismissingAnimation();
@@ -697,10 +715,28 @@
setExpandedHeight(currentMaxPanelHeight);
}
+ private float getStackHeightFraction(float height) {
+ final float gestureFraction = height / getMaxPanelHeight();
+ final float stackHeightFraction = Interpolators.ACCELERATE_DECELERATE
+ .getInterpolation(gestureFraction);
+ return stackHeightFraction;
+ }
+
+ // When the shade is flinged open, add space before sections for overscroll effect.
+ private void maybeOverScrollForShadeFlingOpen(float height) {
+ if (!mBar.isShadeOpening() || mFlingVelocity <= 0) {
+ return;
+ }
+ final float padding = MathUtils.lerp(
+ MIN_OVERSCROLL, MAX_OVERSCROLL, getStackHeightFraction(height));
+ setSectionPadding(padding);
+ }
+
public void setExpandedHeightInternal(float h) {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
+ maybeOverScrollForShadeFlingOpen(h);
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -742,6 +778,8 @@
protected abstract void setIsShadeOpening(boolean isShadeOpening);
+ protected abstract void setSectionPadding(float padding);
+
protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
protected abstract void onHeightUpdated(float expandedHeight);
@@ -1075,11 +1113,6 @@
protected abstract boolean isClearAllVisible();
- /**
- * @return the height of the clear all button, in pixels including padding
- */
- protected abstract int getClearAllHeightWithPadding();
-
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 5a7c5c9..f65f97a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -271,7 +271,7 @@
float contentStart = getPaddingStart();
int childCount = getChildCount();
// Underflow === don't show content until that index
- if (DEBUG) android.util.Log.d(TAG, "calculateIconTranslations: start=" + translationX
+ if (DEBUG) Log.d(TAG, "calculateIconTranslations: start=" + translationX
+ " width=" + width + " underflow=" + mNeedsUnderflow);
// Collect all of the states which want to be visible
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index f45178c..90f5577 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -43,6 +43,8 @@
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import java.util.ArrayList;
+
/**
* Manages the user switcher on the Keyguard.
*/
@@ -53,7 +55,7 @@
private final Container mUserSwitcherContainer;
private final KeyguardStatusBarView mStatusBarView;
- private final Adapter mAdapter;
+ private final KeyguardUserAdapter mAdapter;
private final AppearAnimationUtils mAppearAnimationUtils;
private final KeyguardUserSwitcherScrim mBackground;
@@ -76,7 +78,7 @@
mStatusBarView = statusBarView;
mStatusBarView.setKeyguardUserSwitcher(this);
panelViewController.setKeyguardUserSwitcher(this);
- mAdapter = new Adapter(context, userSwitcherController, this);
+ mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
mAdapter.registerDataSetObserver(mDataSetObserver);
mUserSwitcherController = userSwitcherController;
mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
@@ -259,14 +261,16 @@
}
}
- public static class Adapter extends UserSwitcherController.BaseUserAdapter implements
- View.OnClickListener {
+ static class KeyguardUserAdapter extends
+ UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
private Context mContext;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private View mCurrentUserView;
+ // List of users where the first entry is always the current user
+ private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
- public Adapter(Context context, UserSwitcherController controller,
+ KeyguardUserAdapter(Context context, UserSwitcherController controller,
KeyguardUserSwitcher kgu) {
super(controller);
mContext = context;
@@ -274,15 +278,53 @@
}
@Override
+ public void notifyDataSetChanged() {
+ refreshUserOrder();
+ super.notifyDataSetChanged();
+ }
+
+ void refreshUserOrder() {
+ ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+ mUsersOrdered = new ArrayList<>(users.size());
+ for (int i = 0; i < users.size(); i++) {
+ UserSwitcherController.UserRecord record = users.get(i);
+ if (record.isCurrent) {
+ mUsersOrdered.add(0, record);
+ } else {
+ mUsersOrdered.add(record);
+ }
+ }
+ }
+
+ @Override
+ protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+ return mUsersOrdered;
+ }
+
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
UserSwitcherController.UserRecord item = getItem(position);
- if (!(convertView instanceof UserDetailItemView)
+ return createUserDetailItemView(convertView, parent, item);
+ }
+
+ KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+ if (!(convertView instanceof KeyguardUserDetailItemView)
|| !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
convertView = LayoutInflater.from(mContext).inflate(
R.layout.keyguard_user_switcher_item, parent, false);
- convertView.setOnClickListener(this);
}
- UserDetailItemView v = (UserDetailItemView) convertView;
+ return (KeyguardUserDetailItemView) convertView;
+ }
+
+ UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+ UserSwitcherController.UserRecord item) {
+ KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+ if (!item.isCurrent || item.isGuest) {
+ v.setOnClickListener(this);
+ } else {
+ v.setOnClickListener(null);
+ v.setClickable(false);
+ }
String name = getName(mContext, item);
if (item.picture == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 92d013e..f1ccde7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -294,7 +294,9 @@
}
};
- mWifiManager.registerScanResultsCallback(mReceiverHandler::post, scanResultsCallback);
+ if (mWifiManager != null) {
+ mWifiManager.registerScanResultsCallback(mReceiverHandler::post, scanResultsCallback);
+ }
ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback(){
private Network mLastNetwork;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9a8e26d..0552396 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -605,19 +605,23 @@
controller.addAdapter(new WeakReference<>(this));
}
+ protected ArrayList<UserRecord> getUsers() {
+ return mController.getUsers();
+ }
+
public int getUserCount() {
boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
&& mKeyguardStateController.isMethodSecure()
&& !mKeyguardStateController.canDismissLockScreen();
if (!secureKeyguardShowing) {
- return mController.getUsers().size();
+ return getUsers().size();
}
// The lock screen is secure and showing. Filter out restricted records.
- final int N = mController.getUsers().size();
+ final int userSize = getUsers().size();
int count = 0;
- for (int i = 0; i < N; i++) {
- if (mController.getUsers().get(i).isGuest) continue;
- if (mController.getUsers().get(i).isRestricted) {
+ for (int i = 0; i < userSize; i++) {
+ if (getUsers().get(i).isGuest) continue;
+ if (getUsers().get(i).isRestricted) {
break;
} else {
count++;
@@ -632,13 +636,13 @@
&& mKeyguardStateController.isMethodSecure()
&& !mKeyguardStateController.canDismissLockScreen();
if (!secureKeyguardShowing) {
- return mController.getUsers().size();
+ return getUsers().size();
}
// The lock screen is secure and showing. Filter out restricted records.
- final int N = mController.getUsers().size();
+ final int userSize = getUsers().size();
int count = 0;
- for (int i = 0; i < N; i++) {
- if (mController.getUsers().get(i).isRestricted) {
+ for (int i = 0; i < userSize; i++) {
+ if (getUsers().get(i).isRestricted) {
break;
} else {
count++;
@@ -649,7 +653,7 @@
@Override
public UserRecord getItem(int position) {
- return mController.getUsers().get(position);
+ return getUsers().get(position);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
new file mode 100644
index 0000000..3a77f7ee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.systemsounds;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HomeSoundEffectControllerTest extends SysuiTestCase {
+
+ private @Mock Context mContext;
+ private @Mock AudioManager mAudioManager;
+ private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
+ private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo;
+ private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo;
+
+ private HomeSoundEffectController mController;
+ private TaskStackChangeListener mTaskStackChangeListener;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(WindowConfiguration.ACTIVITY_TYPE_STANDARD).when(
+ mStandardActivityTaskInfo).getActivityType();
+ doReturn(WindowConfiguration.ACTIVITY_TYPE_HOME).when(
+ mHomeActivityTaskInfo).getActivityType();
+
+ mController = new HomeSoundEffectController(mContext, mAudioManager,
+ mTaskStackChangeListeners);
+ }
+
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenHomeFirstMovesToFront() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ @Test
+ public void testHomeSoundEffectPlayedWhenEnabled() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front,
+ mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ @Test
+ public void testHomeSoundEffectNotPlayedTwiceInRow() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front,
+ mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+
+ // If the home task moves to front a second time in a row,
+ mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenNonHomeTaskMovesToFront() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And a standard, non-home task, moves to the front,
+ mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ @Test
+ public void testHomeSoundEffectDisabled() {
+ // When HomeSoundEffectController is started and the home sound effect is disabled,
+ startController(false /* isHomeSoundEffectEnabled */);
+
+ // Then no TaskStackListener should be registered
+ verify(mTaskStackChangeListeners, never()).registerTaskStackListener(
+ any(TaskStackChangeListener.class));
+ }
+
+ /**
+ * Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController.
+ * If the home sound effect is enabled, the registered TaskStackChangeListener is extracted.
+ */
+ private void startController(boolean isHomeSoundEffectEnabled) {
+ // Configure home sound effect to be enabled
+ doReturn(isHomeSoundEffectEnabled).when(mAudioManager).isHomeSoundEffectEnabled();
+
+ mController.start();
+
+ if (isHomeSoundEffectEnabled) {
+ // Construct controller. Save the TaskStackListener for injecting events.
+ final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
+ ArgumentCaptor.forClass(TaskStackChangeListener.class);
+ verify(mTaskStackChangeListeners).registerTaskStackListener(listenerCaptor.capture());
+ mTaskStackChangeListener = listenerCaptor.getValue();
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index c7c1823..ee1d758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -52,6 +52,7 @@
private boolean mHasCustomClock;
private boolean mHasVisibleNotifs;
private float mQsExpansion;
+ private int mCutoutTopInset = 0; // in pixels
@Before
public void setUp() {
@@ -394,7 +395,8 @@
mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion);
+ 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
+ mQsExpansion, mCutoutTopInset);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
new file mode 100644
index 0000000..fc1a791
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.UserDetailItemView
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardUserSwitcherAdapterTest : SysuiTestCase() {
+ @Mock
+ private lateinit var userSwitcherController: UserSwitcherController
+ @Mock
+ private lateinit var parent: ViewGroup
+ @Mock
+ private lateinit var keyguardUserDetailItemView: KeyguardUserDetailItemView
+ @Mock
+ private lateinit var otherView: View
+ @Mock
+ private lateinit var inflatedUserDetailItemView: KeyguardUserDetailItemView
+ @Mock
+ private lateinit var userInfo: UserInfo
+ @Mock
+ private lateinit var layoutInflater: LayoutInflater
+ @Mock
+ private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+
+ private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+ private lateinit var picture: Bitmap
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
+ `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
+ .thenReturn(inflatedUserDetailItemView)
+ adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
+ keyguardUserSwitcher)
+ picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
+ }
+
+ /**
+ * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView has an
+ * incompatible type
+ */
+ private fun createViewFromDifferentType(
+ isCurrentUser: Boolean,
+ isGuestUser: Boolean
+ ): UserDetailItemView? {
+ val user = createUserRecord(isCurrentUser, isGuestUser)
+ return adapter.createUserDetailItemView(otherView, parent, user)
+ }
+
+ /**
+ * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView is an
+ * instance of KeyguardUserDetailItemView
+ */
+ private fun createViewFromSameType(
+ isCurrentUser: Boolean,
+ isGuestUser: Boolean
+ ): UserDetailItemView? {
+ val user = createUserRecord(isCurrentUser, isGuestUser)
+ return adapter.createUserDetailItemView(keyguardUserDetailItemView, parent, user)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsSameType() {
+ val v: UserDetailItemView? = createViewFromSameType(
+ isCurrentUser = false, isGuestUser = false)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsSameType() {
+ val v: UserDetailItemView? = createViewFromSameType(
+ isCurrentUser = false, isGuestUser = true)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+ val v: UserDetailItemView? = createViewFromSameType(
+ isCurrentUser = true, isGuestUser = false)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(null)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsSameType() {
+ val v: UserDetailItemView? = createViewFromSameType(
+ isCurrentUser = true, isGuestUser = true)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsDifferentType() {
+ val v: UserDetailItemView? = createViewFromDifferentType(
+ isCurrentUser = false, isGuestUser = false)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsDifferentType() {
+ val v: UserDetailItemView? = createViewFromDifferentType(
+ isCurrentUser = false, isGuestUser = true)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+ val v: UserDetailItemView? = createViewFromDifferentType(
+ isCurrentUser = true, isGuestUser = false)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(null)
+ }
+
+ @Test
+ fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsDifferentType() {
+ val v: UserDetailItemView? = createViewFromDifferentType(
+ isCurrentUser = true, isGuestUser = true)
+ assertNotNull(v)
+ verify(v)!!.setOnClickListener(adapter)
+ }
+
+ @Test
+ fun testCurrentUserIsAlwaysFirst() {
+ `when`(userSwitcherController.users).thenReturn(arrayListOf(
+ createUserRecord(isCurrentUser = false, isGuestUser = false),
+ createUserRecord(isCurrentUser = true, isGuestUser = false),
+ createUserRecord(isCurrentUser = false, isGuestUser = true),
+ createUserRecord(isCurrentUser = false, isGuestUser = false)
+ ))
+
+ adapter.notifyDataSetChanged()
+ assertTrue("Expected current user to be first in list", adapter.getItem(0).isCurrent)
+ assertFalse("Did not expect current user in position 1", adapter.getItem(1).isCurrent)
+ assertFalse("Did not expect current user in position 2", adapter.getItem(2).isCurrent)
+ assertTrue("Expected guest user to remain in position 2", adapter.getItem(2).isGuest)
+ assertFalse("Did not expect current user in position 3", adapter.getItem(3).isCurrent)
+ }
+
+ private fun createUserRecord(isCurrentUser: Boolean, isGuestUser: Boolean) =
+ UserSwitcherController.UserRecord(
+ userInfo,
+ picture,
+ isGuestUser,
+ isCurrentUser,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index c34285e..bab6deb 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -394,7 +394,7 @@
static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.EMERGENCY_GESTURE_ENABLED, 0, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
}
/**
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index b14ce1c..a6cfae4 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -8,7 +8,7 @@
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
# Userspace reboot
-per-file UserspaceRebootLogger.java = ioffe@google.com, tomcherry@google.com
+per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com
# Sensor Privacy
per-file SensorPrivacyService.java = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index e99bb24..0aee780 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 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.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.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.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 @@
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 @@
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 @@
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 @@
}
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 @@
@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 @@
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 @@
sensor = CAMERA;
}
- onSensorUseStarted(uid, packageName, sensor);
+ long token = Binder.clearCallingIdentity();
+ try {
+ onSensorUseStarted(uid, packageName, sensor);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -203,31 +238,138 @@
* @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 evanseverson: - Implement final UX for notification
- // - Finalize strings and icons and add as resources
+ // TODO: Handle reminders with multiple sensors
- int icon;
- CharSequence notificationMessage;
+ // - 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.
+
+ 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;
+ }
+
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 @@
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 @@
}
@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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index dde182b..464b6ed 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,7 +52,6 @@
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
-import android.telephony.CellLocation;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.CellSignalStrengthGsm;
@@ -585,8 +584,6 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public TelephonyRegistry(Context context, ConfigurationProvider configurationProvider) {
- CellLocation location = CellLocation.getEmpty();
-
mContext = context;
mConfigurationProvider = configurationProvider;
mBatteryStats = BatteryStatsService.getService();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5cc3274..fcb32a1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -112,7 +112,6 @@
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
@@ -1047,9 +1046,11 @@
@GuardedBy("mAm")
void schedulePendingServiceStartLocked(String packageName, int userId) {
- for (int i = mPendingBringups.size() - 1; i >= 0; i--) {
+ int totalPendings = mPendingBringups.size();
+ for (int i = totalPendings - 1; i >= 0 && totalPendings > 0;) {
final ServiceRecord r = mPendingBringups.keyAt(i);
if (r.userId != userId || !TextUtils.equals(r.packageName, packageName)) {
+ i--;
continue;
}
final ArrayList<Runnable> curPendingBringups = mPendingBringups.valueAt(i);
@@ -1057,8 +1058,22 @@
for (int j = curPendingBringups.size() - 1; j >= 0; j--) {
curPendingBringups.get(j).run();
}
+ curPendingBringups.clear();
}
- mPendingBringups.removeAt(i);
+ // Now, how many remaining ones we have after calling into above runnables
+ final int curTotalPendings = mPendingBringups.size();
+ // Don't call removeAt() here, as it could have been removed already by above runnables
+ mPendingBringups.remove(r);
+ if (totalPendings != curTotalPendings) {
+ // Okay, within the above Runnable.run(), the mPendingBringups is altered.
+ // Restart the loop, it won't call into those finished runnables
+ // since we've cleared the curPendingBringups above.
+ totalPendings = mPendingBringups.size();
+ i = totalPendings - 1;
+ } else {
+ totalPendings = mPendingBringups.size();
+ i--;
+ }
}
}
@@ -1069,10 +1084,13 @@
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
r.callStart = false;
- FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid,
- r.name.getPackageName(), r.name.getClassName(),
- FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START);
- mAm.mBatteryStatsService.noteServiceStartRunning(r.stats);
+
+ final int uid = r.appInfo.uid;
+ final String packageName = r.name.getPackageName();
+ final String serviceName = r.name.getClassName();
+ FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
+ serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START);
+ mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName);
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false, false);
if (error != null) {
return new ComponentName("!!", error);
@@ -1108,10 +1126,13 @@
service.delayedStop = true;
return;
}
- FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, service.appInfo.uid,
- service.name.getPackageName(), service.name.getClassName(),
- FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
- mAm.mBatteryStatsService.noteServiceStopRunning(service.stats);
+
+ final int uid = service.appInfo.uid;
+ final String packageName = service.name.getPackageName();
+ final String serviceName = service.name.getClassName();
+ FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
+ serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
+ mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
service.startRequested = false;
if (service.tracker != null) {
service.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -1267,10 +1288,12 @@
}
}
- FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid,
- r.name.getPackageName(), r.name.getClassName(),
- FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
- mAm.mBatteryStatsService.noteServiceStopRunning(r.stats);
+ final int uid = r.appInfo.uid;
+ final String packageName = r.name.getPackageName();
+ final String serviceName = r.name.getClassName();
+ FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
+ serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
+ mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
r.startRequested = false;
if (r.tracker != null) {
r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -2876,15 +2899,7 @@
final Intent.FilterComparison filter
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
- final BatteryStatsImpl.Uid.Pkg.Serv ss;
- final BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- ss = stats.getServiceStatsLocked(
- sInfo.applicationInfo.uid, name.getPackageName(),
- name.getClassName(), SystemClock.elapsedRealtime(),
- SystemClock.uptimeMillis());
- }
- r = new ServiceRecord(mAm, ss, className, name, definingPackageName,
+ r = new ServiceRecord(mAm, className, name, definingPackageName,
definingUid, filter, sInfo, callingFromFg, res);
r.mRecentCallingPackage = callingPackage;
res.setService(r);
@@ -3428,9 +3443,13 @@
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
- FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_LAUNCH_REPORTED, r.appInfo.uid,
- r.name.getPackageName(), r.name.getClassName());
- mAm.mBatteryStatsService.noteServiceStartLaunch(r.stats);
+
+ final int uid = r.appInfo.uid;
+ final String packageName = r.name.getPackageName();
+ final String serviceName = r.name.getClassName();
+ FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_LAUNCH_REPORTED, uid, packageName,
+ serviceName);
+ mAm.mBatteryStatsService.noteServiceStartLaunch(uid, packageName, serviceName);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
@@ -3777,7 +3796,8 @@
smap.mDelayedStartList.remove(r);
if (r.app != null) {
- mAm.mBatteryStatsService.noteServiceStopLaunch(r.stats);
+ mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName());
r.app.stopService(r);
r.app.updateBoundClientUids();
if (r.whitelistManager) {
@@ -4323,7 +4343,8 @@
// Clear app state from services.
for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
ServiceRecord sr = app.getRunningServiceAt(i);
- mAm.mBatteryStatsService.noteServiceStopLaunch(sr.stats);
+ mAm.mBatteryStatsService.noteServiceStopLaunch(sr.appInfo.uid, sr.name.getPackageName(),
+ sr.name.getClassName());
if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
sr.app.stopService(sr);
sr.app.updateBoundClientUids();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 94643f1..ba8f190 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -94,6 +94,7 @@
static final String KEY_PROCESS_CRASH_COUNT_RESET_INTERVAL =
"process_crash_count_reset_interval";
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
+ static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -131,6 +132,7 @@
private static final int DEFAULT_MAX_PHANTOM_PROCESSES = 32;
private static final int DEFAULT_PROCESS_CRASH_COUNT_RESET_INTERVAL = 12 * 60 * 60 * 1000;
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
+ private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
// Flag stored in the DeviceConfig API.
@@ -379,6 +381,13 @@
// the foreground state.
volatile long mFgsNotificationDeferralInterval = 10_000;
+ /*
+ * At boot time, broadcast receiver ACTION_BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED and
+ * ACTION_PRE_BOOT_COMPLETED are temp allowlisted to start FGS for a duration of time in
+ * milliseconds.
+ */
+ volatile long mBootTimeTempAllowlistDuration = DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION;
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -563,6 +572,9 @@
case KEY_MAX_PHANTOM_PROCESSES:
updateMaxPhantomProcesses();
break;
+ case KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION:
+ updateBootTimeTempAllowListDuration();
+ break;
default:
break;
}
@@ -832,6 +844,13 @@
DEFAULT_FORCE_BACKGROUND_CHECK_ON_RESTRICTED_APPS);
}
+ private void updateBootTimeTempAllowListDuration() {
+ mBootTimeTempAllowlistDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION,
+ DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION);
+ }
+
private void updateImperceptibleKillExemptions() {
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1030,6 +1049,8 @@
pw.println(BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD);
pw.print(" "); pw.print(KEY_MAX_PHANTOM_PROCESSES); pw.print("=");
pw.println(MAX_PHANTOM_PROCESSES);
+ pw.print(" "); pw.print(KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION); pw.print("=");
+ pw.println(mBootTimeTempAllowlistDuration);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1f48aeb..aada21d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6003,23 +6003,21 @@
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
String abiOverride, int zygotePolicyFlags) {
return addAppLocked(info, customProcess, isolated, false /* disableHiddenApiChecks */,
- false /* mountExtStorageFull */, abiOverride, zygotePolicyFlags);
+ abiOverride, zygotePolicyFlags);
}
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- boolean disableHiddenApiChecks, boolean mountExtStorageFull, String abiOverride,
- int zygotePolicyFlags) {
+ boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) {
return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks,
- false /* disableTestApiChecks */, mountExtStorageFull, abiOverride,
- zygotePolicyFlags);
+ false /* disableTestApiChecks */, abiOverride, zygotePolicyFlags);
}
// TODO: Move to ProcessList?
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
boolean disableHiddenApiChecks, boolean disableTestApiChecks,
- boolean mountExtStorageFull, String abiOverride, int zygotePolicyFlags) {
+ String abiOverride, int zygotePolicyFlags) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -6054,8 +6052,7 @@
mPersistentStartingProcesses.add(app);
mProcessList.startProcessLocked(app, new HostingRecord("added application",
customProcess != null ? customProcess : app.processName),
- zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks,
- mountExtStorageFull, abiOverride);
+ zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks, abiOverride);
}
return app;
@@ -7612,10 +7609,10 @@
crashInfo.throwLineNumber);
FrameworkStatsLog.write(FrameworkStatsLog.APP_CRASH_OCCURRED,
- Binder.getCallingUid(),
+ (r != null) ? r.uid : -1,
eventType,
processName,
- Binder.getCallingPid(),
+ (r != null) ? r.pid : -1,
(r != null && r.info != null) ? r.info.packageName : "",
(r != null && r.info != null) ? (r.info.isInstantApp()
? FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__TRUE
@@ -14356,10 +14353,6 @@
"disable hidden API checks");
}
- // TODO(b/158750470): remove
- final boolean mountExtStorageFull = isCallerShell()
- && (flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) != 0;
-
final long origId = Binder.clearCallingIdentity();
ProcessRecord app;
@@ -14375,8 +14368,7 @@
UsageEvents.Event.SYSTEM_INTERACTION);
}
app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks,
- disableTestApiChecks, mountExtStorageFull, abiOverride,
- ZYGOTE_POLICY_FLAG_EMPTY);
+ disableTestApiChecks, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY);
}
@@ -16810,6 +16802,13 @@
public Intent getIntentForIntentSender(IIntentSender sender) {
return ActivityManagerService.this.getIntentForIntentSender(sender);
}
+
+ @Override
+ public long getBootTimeTempAllowListDuration() {
+ // Do not lock ActivityManagerService.this here, this API is called by
+ // PackageManagerService.
+ return mConstants.mBootTimeTempAllowlistDuration;
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 405ee72..4fb7abb 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -2498,44 +2498,56 @@
}
}
- void noteServiceStartRunning(final BatteryStatsImpl.Uid.Pkg.Serv stats) {
+ void noteServiceStartRunning(int uid, String pkg, String name) {
synchronized (mLock) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
+ final BatteryStatsImpl.Uid.Pkg.Serv stats = mStats.getServiceStatsLocked(uid,
+ pkg, name, elapsedRealtime, uptime);
stats.startRunningLocked(uptime);
}
});
}
}
- void noteServiceStopRunning(final BatteryStatsImpl.Uid.Pkg.Serv stats) {
+ void noteServiceStopRunning(int uid, String pkg, String name) {
synchronized (mLock) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
+ final BatteryStatsImpl.Uid.Pkg.Serv stats = mStats.getServiceStatsLocked(uid,
+ pkg, name, elapsedRealtime, uptime);
stats.stopRunningLocked(uptime);
}
});
}
}
- void noteServiceStartLaunch(final BatteryStatsImpl.Uid.Pkg.Serv stats) {
+ void noteServiceStartLaunch(int uid, String pkg, String name) {
synchronized (mLock) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
+ final BatteryStatsImpl.Uid.Pkg.Serv stats = mStats.getServiceStatsLocked(uid,
+ pkg, name, elapsedRealtime, uptime);
stats.startLaunchedLocked(uptime);
}
});
}
}
- void noteServiceStopLaunch(final BatteryStatsImpl.Uid.Pkg.Serv stats) {
+ void noteServiceStopLaunch(int uid, String pkg, String name) {
synchronized (mLock) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
+ final BatteryStatsImpl.Uid.Pkg.Serv stats = mStats.getServiceStatsLocked(uid,
+ pkg, name, elapsedRealtime, uptime);
stats.stopLaunchedLocked(uptime);
}
});
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index be63dd4..60b2467 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -18,7 +18,9 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -39,6 +41,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ProgressReporter;
+import com.android.server.LocalServices;
import com.android.server.UiThread;
import java.util.List;
@@ -107,9 +110,20 @@
EventLogTags.writeAmPreBoot(mUserId, componentName.getPackageName());
mIntent.setComponent(componentName);
+ 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,
+ duration);
synchronized (mService) {
mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null,
- null, AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+ null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
+ false, ActivityManagerService.MY_PID,
Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6f6cad0..47b7e1b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1737,7 +1737,7 @@
@GuardedBy("mService")
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
- boolean mountExtStorageFull, String abiOverride) {
+ String abiOverride) {
if (app.pendingStart) {
return true;
}
@@ -2331,7 +2331,7 @@
int zygotePolicyFlags, String abiOverride) {
return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
- false /* mountExtStorageFull */, abiOverride);
+ abiOverride);
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index d9c83da..8a1b4e3 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -46,7 +46,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.ServiceState;
-import com.android.internal.os.BatteryStatsImpl;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.uri.NeededUriGrants;
@@ -70,7 +69,6 @@
static final int MAX_DONE_EXECUTING_COUNT = 6;
final ActivityManagerService ams;
- final BatteryStatsImpl.Uid.Pkg.Serv stats;
final ComponentName name; // service component.
final ComponentName instanceName; // service component's per-instance name.
final String shortInstanceName; // instanceName.flattenToShortString().
@@ -518,13 +516,11 @@
}
}
- ServiceRecord(ActivityManagerService ams,
- BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
+ ServiceRecord(ActivityManagerService ams, ComponentName name,
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
this.ams = ams;
- this.stats = servStats;
this.name = name;
this.instanceName = instanceName;
shortInstanceName = instanceName.flattenToShortString();
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d73de7c..ffe1d68 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -45,8 +45,10 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Dialog;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
@@ -518,7 +520,9 @@
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID,
+ AppOpsManager.OP_NONE,
+ getTemporaryAppWhitelistBroadcastOptions().toBundle(), true,
+ false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), userId);
}
}
@@ -764,7 +768,9 @@
}
}, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID,
+ AppOpsManager.OP_NONE,
+ getTemporaryAppWhitelistBroadcastOptions().toBundle(), true,
+ false, MY_PID, SYSTEM_UID,
callingUid, callingPid, userId);
});
}
@@ -2804,6 +2810,20 @@
}
}
+ private BroadcastOptions getTemporaryAppWhitelistBroadcastOptions() {
+ 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,
+ duration);
+ return bOptions;
+ }
+
/**
* Helper class to store user journey and session id.
*
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 17627fa..7115c9a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7724,20 +7724,24 @@
private class MyHdmiControlStatusChangeListenerCallback
implements HdmiControlManager.HdmiControlStatusChangeListener {
- public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
+ public void onStatusChange(@HdmiControlManager.HdmiCecControl int isCecEnabled,
+ boolean isCecAvailable) {
synchronized (mHdmiClientLock) {
if (mHdmiManager == null) return;
- updateHdmiCecSinkLocked(isCecEnabled ? isCecAvailable : false);
+ boolean cecEnabled = isCecEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
+ updateHdmiCecSinkLocked(cecEnabled ? isCecAvailable : false);
}
}
};
private class MyHdmiCecVolumeControlFeatureListener
implements HdmiControlManager.HdmiCecVolumeControlFeatureListener {
- public void onHdmiCecVolumeControlFeature(boolean enabled) {
+ public void onHdmiCecVolumeControlFeature(
+ @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
synchronized (mHdmiClientLock) {
if (mHdmiManager == null) return;
- mHdmiCecVolumeControlEnabled = enabled;
+ mHdmiCecVolumeControlEnabled =
+ hdmiCecVolumeControl == HdmiControlManager.VOLUME_CONTROL_ENABLED;
}
}
};
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 5025c4a..b1633b0 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 @@
@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 @@
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 @@
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/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 61e7c89..da76af8 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 @@
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/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index e95447b..c2a30be 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 @@
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 c413c8b..bd57fea 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 @@
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 @@
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/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 95c4cee..6893e72 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 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/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 13e2e4f..589db6c 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 @@
@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/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9376e8d..69686a2 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Environment;
-import android.os.RemoteException;
import android.text.TextUtils;
import android.util.LongArray;
import android.util.LongSparseArray;
@@ -53,7 +52,7 @@
import javax.xml.datatype.DatatypeConfigurationException;
/**
- * This class maintains state relating to platform compatibility changes.
+ * CompatConfig maintains state related to the platform compatibility changes.
*
* <p>It stores the default configuration for each change, and any per-package overrides that have
* been configured.
@@ -65,18 +64,38 @@
@GuardedBy("mChanges")
private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
- private OverrideValidatorImpl mOverrideValidator;
+ private final OverrideValidatorImpl mOverrideValidator;
@VisibleForTesting
CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
}
+ static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
+ CompatConfig config = new CompatConfig(androidBuildClassifier, context);
+ config.initConfigFromLib(Environment.buildPath(
+ Environment.getRootDirectory(), "etc", "compatconfig"));
+ config.initConfigFromLib(Environment.buildPath(
+ Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
+
+ List<ApexManager.ActiveApexInfo> apexes = ApexManager.getInstance().getActiveApexInfos();
+ for (ApexManager.ActiveApexInfo apex : apexes) {
+ config.initConfigFromLib(Environment.buildPath(
+ apex.apexDirectory, "etc", "compatconfig"));
+ }
+ config.invalidateCache();
+ return config;
+ }
+
/**
- * Add a change. This is intended to be used by code that reads change config from the
- * filesystem. This should be done at system startup time.
+ * Adds a change.
*
- * @param change The change to add. Any change with the same ID will be overwritten.
+ * <p>This is intended to be used by code that reads change config from the filesystem. This
+ * should be done at system startup time.
+ *
+ * <p>Any change with the same ID will be overwritten.
+ *
+ * @param change the change to add
*/
void addChange(CompatChange change) {
synchronized (mChanges) {
@@ -86,13 +105,15 @@
}
/**
- * Retrieves the set of disabled changes for a given app. Any change ID not in the returned
- * array is by default enabled for the app.
+ * Retrieves the set of disabled changes for a given app.
*
- * @param app The app in question
- * @return A sorted long array of change IDs. We use a primitive array to minimize memory
- * footprint: Every app process will store this array statically so we aim to reduce
- * overhead as much as possible.
+ * <p>Any change ID not in the returned array is by default enabled for the app.
+ *
+ * <p>We use a primitive array to minimize memory footprint: every app process will store this
+ * array statically so we aim to reduce overhead as much as possible.
+ *
+ * @param app the app in question
+ * @return a sorted long array of change IDs
*/
long[] getDisabledChanges(ApplicationInfo app) {
LongArray disabled = new LongArray();
@@ -110,10 +131,10 @@
}
/**
- * Look up a change ID by name.
+ * Looks up a change ID by name.
*
- * @param name Name of the change to look up
- * @return The change ID, or {@code -1} if no change with that name exists.
+ * @param name name of the change to look up
+ * @return the change ID, or {@code -1} if no change with that name exists
*/
long lookupChangeId(String name) {
synchronized (mChanges) {
@@ -127,10 +148,10 @@
}
/**
- * Find if a given change is enabled for a given application.
+ * Checks if a given change is enabled for a given application.
*
- * @param changeId The ID of the change in question
- * @param app App to check for
+ * @param changeId the ID of the change in question
+ * @param app app to check for
* @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
* change ID is not known, as unknown changes are enabled by default.
*/
@@ -146,10 +167,10 @@
}
/**
- * Find if a given change will be enabled for a given package name, prior to installation.
+ * Checks if a given change will be enabled for a given package name after the installation.
*
- * @param changeId The ID of the change in question
- * @param packageName Package name to check for
+ * @param changeId the ID of the change in question
+ * @param packageName package name to check for
* @return {@code true} if the change would be enabled for this package name. Also returns
* {@code true} if the change ID is not known, as unknown changes are enabled by default.
*/
@@ -165,22 +186,22 @@
}
/**
- * Overrides the enabled state for a given change and app. This method is intended to be used
- * *only* for debugging purposes, ultimately invoked either by an adb command, or from some
- * developer settings UI.
+ * Overrides the enabled state for a given change and app.
*
- * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+ * <p>This method is intended to be used *only* for debugging purposes, ultimately invoked
+ * either by an adb command, or from some developer settings UI.
*
- * @param changeId The ID of the change to be overridden. Note, this call will succeed even
- * if
- * this change is not known; it will only have any effect if any code in the
- * platform is gated on the ID given.
- * @param packageName The app package name to override the change for.
- * @param enabled If the change should be enabled or disabled.
- * @return {@code true} if the change existed before adding the override.
+ * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
+ *
+ * @param changeId the ID of the change to be overridden. Note, this call will succeed even
+ * if this change is not known; it will only have any effect if any code in
+ * the platform is gated on the ID given.
+ * @param packageName the app package name to override the change for
+ * @param enabled if the change should be enabled or disabled
+ * @return {@code true} if the change existed before adding the override
+ * @throws IllegalStateException if overriding is not allowed
*/
- boolean addOverride(long changeId, String packageName, boolean enabled)
- throws SecurityException {
+ boolean addOverride(long changeId, String packageName, boolean enabled) {
boolean alreadyKnown = true;
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -201,18 +222,14 @@
break;
default:
throw new IllegalStateException("Should only be able to override changes that "
- + "are allowed or can be deferred.");
+ + "are allowed or can be deferred.");
}
invalidateCache();
}
return alreadyKnown;
}
- /**
- * Check whether the change is known to the compat config.
- *
- * @return {@code true} if the change is known.
- */
+ /** Checks whether the change is known to the compat config. */
boolean isKnownChangeId(long changeId) {
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
@@ -221,16 +238,13 @@
}
/**
- * Returns the maximum sdk version for which this change can be opted in (or -1 if it is not
- * target sdk gated).
+ * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+ * target SDK gated).
*/
int maxTargetSdkForChangeIdOptIn(long changeId) {
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
- if (c == null) {
- return -1;
- }
- if (c.getEnableSinceTargetSdk() != -1) {
+ if (c != null && c.getEnableSinceTargetSdk() != -1) {
return c.getEnableSinceTargetSdk() - 1;
}
return -1;
@@ -243,10 +257,7 @@
boolean isLoggingOnly(long changeId) {
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
- if (c == null) {
- return false;
- }
- return c.getLoggingOnly();
+ return c != null && c.getLoggingOnly();
}
}
@@ -256,24 +267,21 @@
boolean isDisabled(long changeId) {
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
- if (c == null) {
- return false;
- }
- return c.getDisabled();
+ return c != null && c.getDisabled();
}
}
/**
- * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
- * restores the default behaviour for the given change and app, once any app processes have been
- * restarted.
+ * Removes an override previously added via {@link #addOverride(long, String, boolean)}.
*
- * @param changeId The ID of the change that was overridden.
- * @param packageName The app package name that was overridden.
+ * <p>This restores the default behaviour for the given change and app, once any app processes
+ * have been restarted.
+ *
+ * @param changeId the ID of the change that was overridden
+ * @param packageName the app package name that was overridden
* @return {@code true} if an override existed;
*/
- boolean removeOverride(long changeId, String packageName)
- throws SecurityException {
+ boolean removeOverride(long changeId, String packageName) {
boolean overrideExists = false;
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
@@ -299,13 +307,12 @@
/**
* Overrides the enabled state for a given change and app.
*
- * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+ * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
*
- * @param overrides list of overrides to default changes config.
- * @param packageName app for which the overrides will be applied.
+ * @param overrides list of overrides to default changes config
+ * @param packageName app for which the overrides will be applied
*/
- void addOverrides(CompatibilityChangeConfig overrides, String packageName)
- throws RemoteException, SecurityException {
+ void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
synchronized (mChanges) {
for (Long changeId : overrides.enabledChanges()) {
addOverride(changeId, packageName, true);
@@ -324,9 +331,9 @@
*
* <p>This restores the default behaviour for the given app.
*
- * @param packageName The package for which the overrides should be purged.
+ * @param packageName the package for which the overrides should be purged
*/
- void removePackageOverrides(String packageName) throws SecurityException {
+ void removePackageOverrides(String packageName) {
synchronized (mChanges) {
for (int i = 0; i < mChanges.size(); ++i) {
CompatChange change = mChanges.valueAt(i);
@@ -337,8 +344,7 @@
}
private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
- int targetSdkVersion)
- throws RemoteException {
+ int targetSdkVersion) {
LongArray allowed = new LongArray();
synchronized (mChanges) {
for (int i = 0; i < mChanges.size(); ++i) {
@@ -348,7 +354,7 @@
}
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(change.getId(),
- packageName);
+ packageName);
if (allowedState.state == OverrideAllowedState.ALLOWED) {
allowed.add(change.getId());
}
@@ -361,10 +367,9 @@
* Enables all changes with enabledSinceTargetSdk == {@param targetSdkVersion} for
* {@param packageName}.
*
- * @return The number of changes that were toggled.
+ * @return the number of changes that were toggled
*/
- int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion)
- throws RemoteException {
+ int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
addOverride(changeId, packageName, true);
@@ -372,15 +377,13 @@
return changes.length;
}
-
/**
* Disables all changes with enabledSinceTargetSdk == {@param targetSdkVersion} for
* {@param packageName}.
*
- * @return The number of changes that were toggled.
+ * @return the number of changes that were toggled
*/
- int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion)
- throws RemoteException {
+ int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
addOverride(changeId, packageName, false);
@@ -425,7 +428,7 @@
/**
* Dumps the current list of compatibility config information.
*
- * @param pw The {@link PrintWriter} instance to which the information will be dumped.
+ * @param pw {@link PrintWriter} instance to which the information will be dumped
*/
void dumpConfig(PrintWriter pw) {
synchronized (mChanges) {
@@ -441,13 +444,10 @@
}
/**
- * Get the config for a given app.
+ * Returns config for a given app.
*
- * @param applicationInfo the {@link ApplicationInfo} for which the info should be dumped.
- * @return A {@link CompatibilityChangeConfig} which contains the compat config info for the
- * given app.
+ * @param applicationInfo the {@link ApplicationInfo} for which the info should be dumped
*/
-
CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
Set<Long> enabled = new HashSet<>();
Set<Long> disabled = new HashSet<>();
@@ -467,7 +467,7 @@
/**
* Dumps all the compatibility change information.
*
- * @return An array of {@link CompatibilityChangeInfo} with the current changes.
+ * @return an array of {@link CompatibilityChangeInfo} with the current changes
*/
CompatibilityChangeInfo[] dumpChanges() {
synchronized (mChanges) {
@@ -480,22 +480,6 @@
}
}
- static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
- CompatConfig config = new CompatConfig(androidBuildClassifier, context);
- config.initConfigFromLib(Environment.buildPath(
- Environment.getRootDirectory(), "etc", "compatconfig"));
- config.initConfigFromLib(Environment.buildPath(
- Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
-
- List<ApexManager.ActiveApexInfo> apexes = ApexManager.getInstance().getActiveApexInfos();
- for (ApexManager.ActiveApexInfo apex : apexes) {
- config.initConfigFromLib(Environment.buildPath(
- apex.apexDirectory, "etc", "compatconfig"));
- }
- config.invalidateCache();
- return config;
- }
-
void initConfigFromLib(File libraryDir) {
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.d(TAG, "No directory " + libraryDir + ", skipping");
@@ -526,6 +510,7 @@
private void invalidateCache() {
ChangeIdStateCache.invalidate();
}
+
/**
* Rechecks all the existing overrides for a package.
*/
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 1ea468c..6b2a1c9 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -63,45 +63,43 @@
private final ChangeReporter mChangeReporter;
private final CompatConfig mCompatConfig;
- private static int sMinTargetSdk = Build.VERSION_CODES.Q;
-
public PlatformCompat(Context context) {
mContext = context;
- mChangeReporter = new ChangeReporter(
- ChangeReporter.SOURCE_SYSTEM_SERVER);
+ mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
}
@VisibleForTesting
PlatformCompat(Context context, CompatConfig compatConfig) {
mContext = context;
- mChangeReporter = new ChangeReporter(
- ChangeReporter.SOURCE_SYSTEM_SERVER);
+ mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
mCompatConfig = compatConfig;
+
registerPackageReceiver(context);
}
@Override
public void reportChange(long changeId, ApplicationInfo appInfo) {
- checkCompatChangeLogPermission();
- reportChange(changeId, appInfo.uid,
- ChangeReporter.STATE_LOGGED);
+ reportChangeByUid(changeId, appInfo.uid);
}
@Override
- public void reportChangeByPackageName(long changeId, String packageName, int userId) {
- checkCompatChangeLogPermission();
+ public void reportChangeByPackageName(long changeId, String packageName,
+ @UserIdInt int userId) {
ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
- if (appInfo == null) {
- return;
+ if (appInfo != null) {
+ reportChangeByUid(changeId, appInfo.uid);
}
- reportChange(changeId, appInfo);
}
@Override
public void reportChangeByUid(long changeId, int uid) {
checkCompatChangeLogPermission();
- reportChange(changeId, uid, ChangeReporter.STATE_LOGGED);
+ reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
+ }
+
+ private void reportChangeInternal(long changeId, int uid, int state) {
+ mChangeReporter.reportChange(uid, changeId, state);
}
@Override
@@ -110,28 +108,6 @@
return isChangeEnabledInternal(changeId, appInfo);
}
- /**
- * Internal version of the above method, without logging. Does not perform costly permission
- * check.
- * TODO(b/167551701): Remove this method and add 'loggability' as a changeid property.
- */
- public boolean isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo) {
- return mCompatConfig.isChangeEnabled(changeId, appInfo);
- }
-
- /**
- * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. Does not perform costly
- * permission check.
- */
- public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
- boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
- if (appInfo != null) {
- reportChange(changeId, appInfo.uid,
- enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
- }
- return enabled;
- }
-
@Override
public boolean isChangeEnabledByPackageName(long changeId, String packageName,
@UserIdInt int userId) {
@@ -140,7 +116,7 @@
if (appInfo == null) {
return mCompatConfig.willChangeBeEnabled(changeId, packageName);
}
- return isChangeEnabled(changeId, appInfo);
+ return isChangeEnabledInternal(changeId, appInfo);
}
@Override
@@ -152,81 +128,82 @@
}
boolean enabled = true;
for (String packageName : packages) {
- enabled = enabled && isChangeEnabledByPackageName(changeId, packageName,
+ enabled &= isChangeEnabledByPackageName(changeId, packageName,
UserHandle.getUserId(uid));
}
return enabled;
}
/**
- * Register a listener for change state overrides. Only one listener per change is allowed.
+ * Internal version of the above method, without logging.
*
- * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with
- * packageName before the app is killed upon an override change. The state of a change is not
- * guaranteed to change when {@code listener.onCompatChange(String)} is called.
- *
- * @param changeId to get updates for
- * @param listener the listener that will be called upon a potential change for package.
- * @throws IllegalStateException if a listener was already registered for changeId
- * @returns {@code true} if a change with changeId was already known, or (@code false}
- * otherwise.
+ * <p>Does not perform costly permission check.
+ * TODO(b/167551701): Remove this method and add 'loggability' as a changeid property.
*/
- public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
- return mCompatConfig.registerListener(changeId, listener);
+ public boolean isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo) {
+ return mCompatConfig.isChangeEnabled(changeId, appInfo);
+ }
+
+ /**
+ * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+ *
+ * <p>Does not perform costly permission check.
+ */
+ public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
+ boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+ if (appInfo != null) {
+ reportChangeInternal(changeId, appInfo.uid,
+ enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+ }
+ return enabled;
}
@Override
- public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
- throws RemoteException, SecurityException {
+ public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
checkCompatChangeOverridePermission();
mCompatConfig.addOverrides(overrides, packageName);
killPackage(packageName);
}
@Override
- public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
- throws RemoteException, SecurityException {
+ public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
checkCompatChangeOverridePermission();
mCompatConfig.addOverrides(overrides, packageName);
}
@Override
- public int enableTargetSdkChanges(String packageName, int targetSdkVersion)
- throws RemoteException, SecurityException {
+ public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
checkCompatChangeOverridePermission();
- int numChanges = mCompatConfig.enableTargetSdkChangesForPackage(packageName,
- targetSdkVersion);
+ int numChanges =
+ mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion);
killPackage(packageName);
return numChanges;
}
@Override
- public int disableTargetSdkChanges(String packageName, int targetSdkVersion)
- throws RemoteException, SecurityException {
+ public int disableTargetSdkChanges(String packageName, int targetSdkVersion) {
checkCompatChangeOverridePermission();
- int numChanges = mCompatConfig.disableTargetSdkChangesForPackage(packageName,
- targetSdkVersion);
+ int numChanges =
+ mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion);
killPackage(packageName);
return numChanges;
}
@Override
- public void clearOverrides(String packageName) throws RemoteException, SecurityException {
+ public void clearOverrides(String packageName) {
checkCompatChangeOverridePermission();
mCompatConfig.removePackageOverrides(packageName);
killPackage(packageName);
}
@Override
- public void clearOverridesForTest(String packageName)
- throws RemoteException, SecurityException {
+ public void clearOverridesForTest(String packageName) {
checkCompatChangeOverridePermission();
mCompatConfig.removePackageOverrides(packageName);
}
@Override
- public boolean clearOverride(long changeId, String packageName)
- throws RemoteException, SecurityException {
+ public boolean clearOverride(long changeId, String packageName) {
checkCompatChangeOverridePermission();
boolean existed = mCompatConfig.removeOverride(changeId, packageName);
killPackage(packageName);
@@ -234,6 +211,12 @@
}
@Override
+ public void clearOverrideForTest(long changeId, String packageName) {
+ checkCompatChangeOverridePermission();
+ mCompatConfig.removeOverride(changeId, packageName);
+ }
+
+ @Override
public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
checkCompatChangeReadAndLogPermission();
return mCompatConfig.getAppConfig(appInfo);
@@ -247,18 +230,13 @@
@Override
public CompatibilityChangeInfo[] listUIChanges() {
- return Arrays.stream(listAllChanges()).filter(
- x -> isShownInUI(x)).toArray(CompatibilityChangeInfo[]::new);
+ return Arrays.stream(listAllChanges()).filter(this::isShownInUI).toArray(
+ CompatibilityChangeInfo[]::new);
}
- /**
- * Check whether the change is known to the compat config.
- *
- * @return {@code true} if the change is known.
- */
+ /** Checks whether the change is known to the compat config. */
public boolean isKnownChangeId(long changeId) {
return mCompatConfig.isKnownChangeId(changeId);
-
}
/**
@@ -286,7 +264,9 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) {
+ return;
+ }
checkCompatChangeReadAndLogPermission();
mCompatConfig.dumpConfig(pw);
}
@@ -298,7 +278,8 @@
/**
* Clears information stored about events reported on behalf of an app.
- * To be called once upon app start or end. A second call would be a no-op.
+ *
+ * <p>To be called once upon app start or end. A second call would be a no-op.
*
* @param appInfo the app to reset
*/
@@ -311,13 +292,9 @@
packageName, 0, userId, userId);
}
- private void reportChange(long changeId, int uid, int state) {
- mChangeReporter.reportChange(uid, changeId, state);
- }
-
private void killPackage(String packageName) {
int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
- 0, UserHandle.myUserId());
+ 0, UserHandle.myUserId());
if (uid < 0) {
Slog.w(TAG, "Didn't find package " + packageName + " on device.");
@@ -325,21 +302,18 @@
}
Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ").");
- killUid(UserHandle.getAppId(uid),
- UserHandle.USER_ALL, "PlatformCompat overrides");
+ killUid(UserHandle.getAppId(uid));
}
- private void killUid(int appId, int userId, String reason) {
+ private void killUid(int appId) {
final long identity = Binder.clearCallingIdentity();
try {
IActivityManager am = ActivityManager.getService();
if (am != null) {
- try {
- am.killUid(appId, userId, reason);
- } catch (RemoteException e) {
- /* ignore - same process */
- }
+ am.killUid(appId, UserHandle.USER_ALL, "PlatformCompat overrides");
}
+ } catch (RemoteException e) {
+ /* ignore - same process */
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -350,13 +324,12 @@
if (Binder.getCallingUid() == SYSTEM_UID) {
return;
}
- if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE)
- != PERMISSION_GRANTED) {
+ if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE) != PERMISSION_GRANTED) {
throw new SecurityException("Cannot log compat change usage");
}
}
- private void checkCompatChangeReadPermission() throws SecurityException {
+ private void checkCompatChangeReadPermission() {
// Don't check for permissions within the system process
if (Binder.getCallingUid() == SYSTEM_UID) {
return;
@@ -367,7 +340,7 @@
}
}
- private void checkCompatChangeOverridePermission() throws SecurityException {
+ private void checkCompatChangeOverridePermission() {
// Don't check for permissions within the system process
if (Binder.getCallingUid() == SYSTEM_UID) {
return;
@@ -378,7 +351,7 @@
}
}
- private void checkCompatChangeReadAndLogPermission() throws SecurityException {
+ private void checkCompatChangeReadAndLogPermission() {
checkCompatChangeReadPermission();
checkCompatChangeLogPermission();
}
@@ -391,16 +364,34 @@
return false;
}
if (change.getEnableSinceTargetSdk() > 0) {
- if (change.getEnableSinceTargetSdk() < sMinTargetSdk) {
- return false;
- }
+ return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q;
}
return true;
}
/**
+ * Registers a listener for change state overrides.
+ *
+ * <p>Only one listener per change is allowed.
+ *
+ * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with
+ * packageName before the app is killed upon an override change. The state of a change is not
+ * guaranteed to change when {@code listener.onCompatChange(String)} is called.
+ *
+ * @param changeId to get updates for
+ * @param listener the listener that will be called upon a potential change for package
+ * @return {@code true} if a change with changeId was already known, or (@code false}
+ * otherwise
+ * @throws IllegalStateException if a listener was already registered for changeId
+ */
+ public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
+ return mCompatConfig.registerListener(changeId, listener);
+ }
+
+ /**
* Registers a broadcast receiver that listens for package install, replace or remove.
- * @param context the context where the receiver should be registered.
+ *
+ * @param context the context where the receiver should be registered
*/
public void registerPackageReceiver(Context context) {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@@ -429,8 +420,8 @@
}
/**
- * Register the observer for
- * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}
+ * Registers the observer for
+ * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}.
*/
public void registerContentObserver() {
mCompatConfig.registerContentObserver();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 16695d1..e854481 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -291,6 +291,8 @@
return STORAGE_GLOBAL_SETTINGS;
case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
return STORAGE_GLOBAL_SETTINGS;
+ case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
+ return STORAGE_GLOBAL_SETTINGS;
case HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING:
@@ -309,6 +311,8 @@
return Global.HDMI_CEC_VERSION;
case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
return Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP;
+ case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
+ return Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED;
case HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING:
@@ -323,13 +327,13 @@
@Storage int storage = getStorage(setting);
String storageKey = getStorageKey(setting);
if (storage == STORAGE_SYSPROPS) {
- Slog.d(TAG, "Reading '" + storageKey + "' sysprop.");
+ HdmiLogger.debug("Reading '" + storageKey + "' sysprop.");
return mStorageAdapter.retrieveSystemProperty(storageKey, defaultValue);
} else if (storage == STORAGE_GLOBAL_SETTINGS) {
- Slog.d(TAG, "Reading '" + storageKey + "' global setting.");
+ HdmiLogger.debug("Reading '" + storageKey + "' global setting.");
return mStorageAdapter.retrieveGlobalSetting(storageKey, defaultValue);
} else if (storage == STORAGE_SHARED_PREFS) {
- Slog.d(TAG, "Reading '" + storageKey + "' shared preference.");
+ HdmiLogger.debug("Reading '" + storageKey + "' shared preference.");
return mStorageAdapter.retrieveSharedPref(storageKey, defaultValue);
}
return null;
@@ -339,13 +343,13 @@
@Storage int storage = getStorage(setting);
String storageKey = getStorageKey(setting);
if (storage == STORAGE_SYSPROPS) {
- Slog.d(TAG, "Setting '" + storageKey + "' sysprop.");
+ HdmiLogger.debug("Setting '" + storageKey + "' sysprop.");
mStorageAdapter.storeSystemProperty(storageKey, value);
} else if (storage == STORAGE_GLOBAL_SETTINGS) {
- Slog.d(TAG, "Setting '" + storageKey + "' global setting.");
+ HdmiLogger.debug("Setting '" + storageKey + "' global setting.");
mStorageAdapter.storeGlobalSetting(storageKey, value);
} else if (storage == STORAGE_SHARED_PREFS) {
- Slog.d(TAG, "Setting '" + storageKey + "' shared pref.");
+ HdmiLogger.debug("Setting '" + storageKey + "' shared pref.");
mStorageAdapter.storeSharedPref(storageKey, value);
notifySettingChanged(setting);
}
@@ -366,6 +370,9 @@
case Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP:
notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
break;
+ case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
+ notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
+ break;
}
}
@@ -399,6 +406,7 @@
Global.HDMI_CONTROL_ENABLED,
Global.HDMI_CEC_VERSION,
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
};
for (String setting: settings) {
resolver.registerContentObserver(Global.getUriFor(setting), false,
@@ -597,7 +605,7 @@
throw new IllegalArgumentException("Setting '" + name
+ "' is not a string-type setting.");
}
- Slog.d(TAG, "Getting CEC setting value '" + name + "'.");
+ HdmiLogger.debug("Getting CEC setting value '" + name + "'.");
return retrieveValue(setting, setting.getDefaultValue().getStringValue());
}
@@ -613,7 +621,7 @@
throw new IllegalArgumentException("Setting '" + name
+ "' is not a int-type setting.");
}
- Slog.d(TAG, "Getting CEC setting value '" + name + "'.");
+ HdmiLogger.debug("Getting CEC setting value '" + name + "'.");
String defaultValue = Integer.toString(getIntValue(setting.getDefaultValue()));
String value = retrieveValue(setting, defaultValue);
return Integer.parseInt(value);
@@ -638,7 +646,7 @@
throw new IllegalArgumentException("Invalid CEC setting '" + name
+ "' value: '" + value + "'.");
}
- Slog.d(TAG, "Updating CEC setting '" + name + "' to '" + value + "'.");
+ HdmiLogger.debug("Updating CEC setting '" + name + "' to '" + value + "'.");
storeValue(setting, value);
}
@@ -661,7 +669,7 @@
throw new IllegalArgumentException("Invalid CEC setting '" + name
+ "' value: '" + value + "'.");
}
- Slog.d(TAG, "Updating CEC setting '" + name + "' to '" + value + "'.");
+ HdmiLogger.debug("Updating CEC setting '" + name + "' to '" + value + "'.");
storeValue(setting, Integer.toString(value));
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 0b10cc3..ccce9dc 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -619,7 +619,9 @@
} else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
mService.wakeUp();
return true;
- } else if (!mService.isHdmiCecVolumeControlEnabled() && isVolumeOrMuteCommand(message)) {
+ } else if (mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand(
+ message)) {
return false;
}
@@ -1142,7 +1144,8 @@
@ServiceThreadOnly
protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) {
assertRunOnServiceThread();
- if (!mService.isHdmiCecVolumeControlEnabled()) {
+ if (mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index a945c90..b909b16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -373,7 +373,8 @@
@ServiceThreadOnly
protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (isSystemAudioControlFeatureEnabled() && mService.isHdmiCecVolumeControlEnabled()) {
+ if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_ENABLED) {
reportAudioStatus(message.getSource());
} else {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
@@ -723,7 +724,8 @@
void reportAudioStatus(int source) {
assertRunOnServiceThread();
- if (!mService.isHdmiCecVolumeControlEnabled()) {
+ if (mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index ad3773e..e568aa2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -618,7 +618,8 @@
@ServiceThreadOnly
protected boolean handleReportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!mService.isHdmiCecVolumeControlEnabled()) {
+ if (mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return false;
}
@@ -897,7 +898,8 @@
}
void setAudioStatus(boolean mute, int volume) {
- if (!isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) {
+ if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
synchronized (mLock) {
@@ -919,7 +921,8 @@
// On initialization process, getAvrDeviceInfo() may return null and cause exception
return;
}
- if (delta == 0 || !isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) {
+ if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
@@ -948,7 +951,8 @@
@ServiceThreadOnly
void changeMute(boolean mute) {
assertRunOnServiceThread();
- if (getAvrDeviceInfo() == null || !mService.isHdmiCecVolumeControlEnabled()) {
+ if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
// On initialization process, getAvrDeviceInfo() may return null and cause exception
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index b427bd0..9b194ae 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,6 +18,7 @@
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
+import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.DISABLED;
@@ -206,7 +207,8 @@
// Whether HDMI CEC volume control is enabled or not.
@GuardedBy("mLock")
- private boolean mHdmiCecVolumeControlEnabled;
+ @HdmiControlManager.VolumeControl
+ private int mHdmiCecVolumeControl;
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
private final HdmiCecConfig mHdmiCecConfig;
@@ -324,7 +326,8 @@
// Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
// handling will be disabled and no request will be handled.
@GuardedBy("mLock")
- private boolean mHdmiControlEnabled;
+ @HdmiControlManager.HdmiCecControl
+ private int mHdmiControlEnabled;
// Set to true while the service is in normal mode. While set to false, no input change is
// allowed. Used for situations where input change can confuse users such as channel auto-scan,
@@ -476,10 +479,9 @@
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
mProhibitMode = false;
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
- HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)
- == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
- mHdmiCecVolumeControlEnabled = readBooleanSetting(
- Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+ setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
if (mCecController == null) {
@@ -496,7 +498,7 @@
Slog.i(TAG, "Device does not support MHL-control.");
}
mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
- if (mHdmiControlEnabled) {
+ if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
mCecController.setOption(OptionKey.ENABLE_CEC, false);
@@ -512,9 +514,8 @@
new HdmiCecConfig.SettingChangeListener() {
@Override
public void onChange(String setting) {
- boolean enabled = mHdmiCecConfig.getIntValue(
- HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)
- == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
+ @HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
setControlEnabled(enabled);
}
});
@@ -629,7 +630,7 @@
}
if (reason != -1) {
invokeVendorCommandListenersOnControlStateChanged(true, reason);
- announceHdmiControlStatusChange(true);
+ announceHdmiControlStatusChange(HDMI_CEC_CONTROL_ENABLED);
}
}
@@ -676,7 +677,8 @@
boolean enabled = readBooleanSetting(option, true);
switch (option) {
case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
- setHdmiCecVolumeControlEnabledInternal(enabled);
+ setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
break;
case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
if (isTvDeviceEnabled()) {
@@ -1212,7 +1214,8 @@
void setAudioStatus(boolean mute, int volume) {
if (!isTvDeviceEnabled()
|| !tv().isSystemAudioActivated()
- || !isHdmiCecVolumeControlEnabled()) {
+ || getHdmiCecVolumeControl()
+ == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
AudioManager audioManager = getAudioManager();
@@ -2120,24 +2123,6 @@
}
@Override
- public boolean isHdmiCecVolumeControlEnabled() {
- initBinderCall();
- return HdmiControlService.this.isHdmiCecVolumeControlEnabled();
- }
-
- @Override
- public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
- initBinderCall();
- final long token = Binder.clearCallingIdentity();
- try {
- HdmiControlService.this.setHdmiCecVolumeControlEnabled(
- isHdmiCecVolumeControlEnabled);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
final boolean isMute) {
initBinderCall();
@@ -2208,10 +2193,9 @@
// System settings
pw.println("System_settings:");
pw.increaseIndent();
- pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
- pw.println("mHdmiCecVolumeControlEnabled: " + mHdmiCecVolumeControlEnabled);
+ pw.println("mHdmiCecVolumeControlEnabled: " + mHdmiCecVolumeControl);
pw.decreaseIndent();
// CEC settings
@@ -2340,6 +2324,13 @@
}
}
+ @VisibleForTesting
+ void setHdmiCecVolumeControlEnabledInternal(
+ @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
+ mHdmiCecVolumeControl = hdmiCecVolumeControl;
+ announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
+ }
+
// Get the source address to send out commands to devices connected to the current device
// when other services interact with HdmiControlService.
private int getRemoteControlSourceAddress() {
@@ -2533,10 +2524,10 @@
// Return the current status of mHdmiCecVolumeControlEnabled;
synchronized (mLock) {
try {
- listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControlEnabled);
+ listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControl);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: "
- + mHdmiCecVolumeControlEnabled, e);
+ + mHdmiCecVolumeControl, e);
}
}
}
@@ -2781,7 +2772,7 @@
}
}
- private void announceHdmiControlStatusChange(boolean isEnabled) {
+ private void announceHdmiControlStatusChange(@HdmiControlManager.HdmiCecControl int isEnabled) {
assertRunOnServiceThread();
synchronized (mLock) {
List<IHdmiControlStatusChangeListener> listeners = new ArrayList<>(
@@ -2795,16 +2786,18 @@
}
private void invokeHdmiControlStatusChangeListenerLocked(
- IHdmiControlStatusChangeListener listener, boolean isEnabled) {
+ IHdmiControlStatusChangeListener listener,
+ @HdmiControlManager.HdmiCecControl int isEnabled) {
invokeHdmiControlStatusChangeListenerLocked(Collections.singletonList(listener), isEnabled);
}
private void invokeHdmiControlStatusChangeListenerLocked(
- Collection<IHdmiControlStatusChangeListener> listeners, boolean isEnabled) {
+ Collection<IHdmiControlStatusChangeListener> listeners,
+ @HdmiControlManager.HdmiCecControl int isEnabled) {
if (listeners.isEmpty()) {
return;
}
- if (isEnabled) {
+ if (isEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
queryDisplayStatus(new IHdmiControlCallback.Stub() {
public void onComplete(int status) {
boolean isAvailable = true;
@@ -2822,7 +2815,8 @@
}
private void invokeHdmiControlStatusChangeListenerLocked(
- Collection<IHdmiControlStatusChangeListener> listeners, boolean isEnabled,
+ Collection<IHdmiControlStatusChangeListener> listeners,
+ @HdmiControlManager.HdmiCecControl int isEnabled,
boolean isCecAvailable) {
for (IHdmiControlStatusChangeListener listener : listeners) {
try {
@@ -2835,15 +2829,16 @@
}
}
- private void announceHdmiCecVolumeControlFeatureChange(boolean isEnabled) {
+ private void announceHdmiCecVolumeControlFeatureChange(
+ @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
assertRunOnServiceThread();
mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
try {
- listener.onHdmiCecVolumeControlFeature(isEnabled);
+ listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
} catch (RemoteException e) {
Slog.e(TAG,
"Failed to report HdmiControlVolumeControlStatusChange: "
- + isEnabled);
+ + hdmiCecVolumeControl);
}
});
}
@@ -2888,7 +2883,7 @@
boolean isControlEnabled() {
synchronized (mLock) {
- return mHdmiControlEnabled;
+ return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
}
}
@@ -2962,7 +2957,7 @@
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
false);
if (mCecController != null) {
- if (mHdmiControlEnabled) {
+ if (mHdmiControlEnabled == HDMI_CEC_CONTROL_ENABLED) {
int startReason = -1;
switch (wakeUpAction) {
case WAKE_UP_SCREEN_ON:
@@ -3193,31 +3188,10 @@
}
}
- void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
- setHdmiCecVolumeControlEnabledInternal(isHdmiCecVolumeControlEnabled);
-
- writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
- isHdmiCecVolumeControlEnabled);
- }
-
- @VisibleForTesting
- void setHdmiCecVolumeControlEnabledInternal(boolean isHdmiCecVolumeControlEnabled) {
+ @HdmiControlManager.VolumeControl
+ int getHdmiCecVolumeControl() {
synchronized (mLock) {
- mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
-
- boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
- true);
- if (storedValue != isHdmiCecVolumeControlEnabled) {
- HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s",
- isHdmiCecVolumeControlEnabled);
- }
- }
- announceHdmiCecVolumeControlFeatureChange(isHdmiCecVolumeControlEnabled);
- }
-
- boolean isHdmiCecVolumeControlEnabled() {
- synchronized (mLock) {
- return mHdmiCecVolumeControlEnabled;
+ return mHdmiCecVolumeControl;
}
}
@@ -3252,21 +3226,21 @@
}
@ServiceThreadOnly
- void setControlEnabled(boolean enabled) {
+ void setControlEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
assertRunOnServiceThread();
synchronized (mLock) {
mHdmiControlEnabled = enabled;
}
- if (enabled) {
+ if (enabled == HDMI_CEC_CONTROL_ENABLED) {
enableHdmiControlService();
- setHdmiCecVolumeControlEnabledInternal(
- readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true));
+ setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
return;
}
- setHdmiCecVolumeControlEnabledInternal(false);
+ setHdmiCecVolumeControlEnabledInternal(HdmiControlManager.VOLUME_CONTROL_DISABLED);
// Call the vendor handler before the service is disabled.
invokeVendorCommandListenersOnControlStateChanged(false,
HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
diff --git a/services/core/java/com/android/server/hdmi/cec_config.xml b/services/core/java/com/android/server/hdmi/cec_config.xml
index 480e0ec..e6e3c2f 100644
--- a/services/core/java/com/android/server/hdmi/cec_config.xml
+++ b/services/core/java/com/android/server/hdmi/cec_config.xml
@@ -46,4 +46,13 @@
</allowed-values>
<default-value int-value="1" />
</setting>
+ <setting name="volume_control_enabled"
+ value-type="int"
+ user-configurable="true">
+ <allowed-values>
+ <value int-value="0" />
+ <value int-value="1" />
+ </allowed-values>
+ <default-value int-value="1" />
+ </setting>
</cec-settings>
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ab7e3b0..143ec15 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.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 @@
}
@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 @@
@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 @@
@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);
- }
- 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;
- }
- 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) {
- }
+ // null matrixValues means that the entry needs to be removed.
+ if (matrixValues == null) {
+ final ActivityViewInfo info =
+ mActivityViewDisplayIdToParentMap.get(childDisplayId);
+ if (info == null) {
+ return;
}
- break;
+ if (info.mParentClient != cs) {
+ throw new SecurityException("Only the owner client can clear"
+ + " ActivityViewGeometry for display #" + childDisplayId);
+ }
+ mActivityViewDisplayIdToParentMap.remove(childDisplayId);
+ return;
}
- displayId = info.mParentClient.selfReportedDisplayId;
+
+ 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);
+ }
+ 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;
+ }
+ 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;
+ }
+ 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 3485881..2dd7096 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.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 @@
@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 @@
@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/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 83b6eca..b5b93d6 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -949,7 +949,8 @@
}
@Override
- public boolean isProviderPackage(String provider, String packageName) {
+ public boolean isProviderPackage(@Nullable String provider, String packageName,
+ @Nullable String attributionTag) {
mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null);
for (LocationProviderManager manager : mProviderManagers) {
@@ -960,7 +961,8 @@
if (identity == null) {
continue;
}
- if (identity.getPackageName().equals(packageName)) {
+ if (identity.getPackageName().equals(packageName) && (attributionTag == null
+ || Objects.equals(identity.getAttributionTag(), attributionTag))) {
return true;
}
}
@@ -1262,13 +1264,17 @@
}
@Override
- public boolean isProvider(String provider, CallerIdentity identity) {
- LocationProviderManager manager = getLocationProviderManager(provider);
- if (manager == null) {
- return false;
- } else {
- return identity.equals(manager.getIdentity());
+ public boolean isProvider(@Nullable String provider, CallerIdentity identity) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ if (provider != null && !provider.equals(manager.getName())) {
+ continue;
+ }
+ if (identity.equalsIgnoringListenerId(manager.getIdentity())) {
+ return true;
+ }
}
+
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index b06389a..06ca9ec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1333,9 +1333,6 @@
Binder.restoreCallingIdentity(identity);
}
- setRealProvider(null);
- setMockProvider(null);
-
mUserHelper.removeListener(mUserChangedListener);
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9f351a3..28c90e9 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.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 @@
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 61b218c..c397724 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -998,6 +998,7 @@
REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
reportUserInteraction(r);
+ mAssistants.notifyAssistantNotificationClicked(r);
}
}
@@ -3085,8 +3086,23 @@
synchronized (mToastQueue) {
int uid = Binder.getCallingUid();
+ int userId = UserHandle.getUserId(uid);
if (enable) {
mToastRateLimitingDisabledUids.remove(uid);
+ try {
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null) {
+ Slog.e(TAG, "setToastRateLimitingEnabled method haven't found any "
+ + "packages for the given uid: " + uid + ", toast rate "
+ + "limiter not reset for that uid.");
+ return;
+ }
+ for (String pkg : packages) {
+ mToastRateLimiter.clear(userId, pkg);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to reset toast rate limiter for given uid", e);
+ }
} else {
mToastRateLimitingDisabledUids.add(uid);
}
@@ -9459,6 +9475,22 @@
});
}
+ @GuardedBy("mNotificationLock")
+ void notifyAssistantNotificationClicked(final NotificationRecord r) {
+ final String key = r.getSbn().getKey();
+ notifyAssistantLocked(
+ r.getSbn(),
+ r.getNotificationType(),
+ true /* sameUserOnly */,
+ (assistant, sbnHolder) -> {
+ try {
+ assistant.onNotificationClicked(key);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (clicked): " + assistant, ex);
+ }
+ });
+ }
+
/**
* Notifies the assistant something about the specified notification, only assistant
* that is visible to the notification will be notified.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c93127d..e7a04ef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15071,6 +15071,16 @@
}
final IActivityManager am = ActivityManager.getService();
try {
+ 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,
+ duration);
// Deliver LOCKED_BOOT_COMPLETED first
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
@@ -15079,7 +15089,8 @@
}
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false,
+ requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
+ false, false,
userId);
// Deliver BOOT_COMPLETED only if user is unlocked
@@ -15089,7 +15100,8 @@
bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false,
+ requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
+ false, false,
userId);
}
} catch (RemoteException e) {
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 0000000..9fa9b29
--- /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/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
new file mode 100644
index 0000000..fb75ae1
--- /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 0e8fd8f..da3e48a 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.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 @@
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 @@
mStrongAuthTracker = new StrongAuthTracker(context);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mSettingsObserver = new SettingsObserver(mHandler);
+ mAuthorizationService = new Authorization();
}
@Override
@@ -699,11 +703,13 @@
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 @@
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/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
index b77df2d..9bf046c 100644
--- a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -27,7 +27,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.LongArrayQueue;
import android.util.Slog;
@@ -294,12 +293,11 @@
@Override
@GuardedBy("mLock")
- void handleRemovedAppLocked(String packageName, int uid) {
+ void handleRemovedAppLocked(final int userId, @NonNull String packageName) {
if (packageName == null) {
Slog.wtf(TAG, "Told app removed but given null package name.");
return;
}
- final int userId = UserHandle.getUserId(uid);
mEventTimes.delete(userId, packageName);
mExecutionStatsCache.delete(userId, packageName);
diff --git a/services/core/java/com/android/server/utils/quota/MultiRateLimiter.java b/services/core/java/com/android/server/utils/quota/MultiRateLimiter.java
index fdbe4b4..a8daa3b 100644
--- a/services/core/java/com/android/server/utils/quota/MultiRateLimiter.java
+++ b/services/core/java/com/android/server/utils/quota/MultiRateLimiter.java
@@ -48,6 +48,7 @@
* @hide
*/
public class MultiRateLimiter {
+ private static final String TAG = "MultiRateLimiter";
private static final CountQuotaTracker[] EMPTY_TRACKER_ARRAY = {};
@@ -73,6 +74,13 @@
}
}
+ /** Remove all saved events from the rate limiter for the given app (reset it). */
+ public void clear(int userId, @NonNull String packageName) {
+ synchronized (mLock) {
+ clearLocked(userId, packageName);
+ }
+ }
+
@GuardedBy("mLock")
private void noteEventLocked(int userId, @NonNull String packageName, @Nullable String tag) {
for (CountQuotaTracker quotaTracker : mQuotaTrackers) {
@@ -91,6 +99,16 @@
return true;
}
+ @GuardedBy("mLock")
+ private void clearLocked(int userId, @NonNull String packageName) {
+ for (CountQuotaTracker quotaTracker : mQuotaTrackers) {
+ // This method behaves as if the package has been removed from the device, which
+ // isn't the case here, but it does similar clean-up to what we are aiming for here,
+ // so it works for this use case.
+ quotaTracker.onAppRemovedLocked(userId, packageName);
+ }
+ }
+
/** Can create a new {@link MultiRateLimiter}. */
public static class Builder {
@@ -98,7 +116,8 @@
private final Context mContext;
private final Categorizer mCategorizer;
private final Category mCategory;
- @Nullable private final QuotaTracker.Injector mInjector;
+ @Nullable
+ private final QuotaTracker.Injector mInjector;
/**
* Creates a new builder and allows to inject an object that can be used
@@ -121,7 +140,7 @@
/**
* Adds another rate limit to be used in {@link MultiRateLimiter}.
*
- * @param limit The maximum event count an app can have in the rolling time window.
+ * @param limit The maximum event count an app can have in the rolling time window.
* @param windowSize The rolling time window to use when checking quota usage.
*/
public Builder addRateLimit(int limit, Duration windowSize) {
@@ -164,7 +183,7 @@
public final Duration mWindowSize;
/**
- * @param limit The maximum count of some occurrence in the rolling time window.
+ * @param limit The maximum count of some occurrence in the rolling time window.
* @param windowSize The rolling time window to use when checking quota usage.
*/
private RateLimit(int limit, Duration windowSize) {
@@ -173,7 +192,7 @@
}
/**
- * @param limit The maximum count of some occurrence in the rolling time window.
+ * @param limit The maximum count of some occurrence in the rolling time window.
* @param windowSize The rolling time window to use when checking quota usage.
*/
public static RateLimit create(int limit, Duration windowSize) {
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index 7f44618..802ab5b 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -58,7 +58,7 @@
* of quota until it is below that limit again. Limits are applied according to the category
* the UPTC is placed in. Categories are basic constructs to apply different limits to
* different groups of UPTCs. For example, standby buckets can be a set of categories, or
- * foreground & background could be two categories. If every UPTC should have the limits
+ * foreground & background could be two categories. If every UPTC should have the same limits
* applied, then only one category is needed.
*
* Note: all limits are enforced per category unless explicitly stated otherwise.
@@ -132,7 +132,7 @@
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
- onAppRemovedLocked(getPackageName(intent), uid);
+ onAppRemovedLocked(UserHandle.getUserId(uid), getPackageName(intent));
}
break;
case Intent.ACTION_USER_REMOVED:
@@ -358,21 +358,20 @@
}
@GuardedBy("mLock")
- abstract void handleRemovedAppLocked(String packageName, int uid);
+ abstract void handleRemovedAppLocked(int userId, @NonNull String packageName);
@GuardedBy("mLock")
- private void onAppRemovedLocked(String packageName, int uid) {
+ void onAppRemovedLocked(final int userId, @NonNull String packageName) {
if (packageName == null) {
Slog.wtf(TAG, "Told app removed but given null package name.");
return;
}
- final int userId = UserHandle.getUserId(uid);
mInQuotaAlarmListener.removeAlarmsLocked(userId, packageName);
mFreeQuota.delete(userId, packageName);
- handleRemovedAppLocked(packageName, uid);
+ handleRemovedAppLocked(userId, packageName);
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f9cc334..0f4bb4c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2606,7 +2606,7 @@
finishActivityResults(resultCode, resultData, resultGrants);
- final boolean endTask = task.getActivityBelow(this) == null
+ final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
final int transit = endTask ? TRANSIT_OLD_TASK_CLOSE : TRANSIT_OLD_ACTIVITY_CLOSE;
if (isState(RESUMED)) {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 0fc4cdb..d90e885 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -662,7 +662,9 @@
pw.println(getName()
+ " type=" + activityTypeToString(getActivityType())
+ " mode=" + windowingModeToString(getWindowingMode())
- + " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode()));
+ + " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode())
+ + " requested-bounds=" + getRequestedOverrideBounds().toShortString()
+ + " bounds=" + getBounds().toShortString());
for (int i = getChildCount() - 1; i >= 0; --i) {
final E cc = getChildAt(i);
pw.print(childPrefix + "#" + i + " ");
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index a20d924..cc77cfa 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 @@
}
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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 855c8f5..2295ee3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -29,6 +29,7 @@
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -433,6 +434,7 @@
CharSequence lastDescription; // Last description captured for this item.
+ Task mAdjacentTask; // Task adjacent to this one.
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
Task mPrevAffiliate; // previous task in affiliated chain.
int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
@@ -1564,6 +1566,11 @@
mTaskId, mUserId);
}
+ void setAdjacentTask(Task adjacent) {
+ mAdjacentTask = adjacent;
+ adjacent.mAdjacentTask = this;
+ }
+
void setTaskToAffiliateWith(Task taskToAffiliateWith) {
closeRecentsChain();
mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
@@ -4244,6 +4251,7 @@
}
}
+ final List<Task> adjacentTasks = new ArrayList<>();
final int windowingMode = getWindowingMode();
final boolean isAssistantType = isActivityTypeAssistant();
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
@@ -4273,6 +4281,15 @@
continue;
}
return TASK_VISIBILITY_INVISIBLE;
+ } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ && other.matchParentBounds()) {
+ if (other.isTranslucent(starting)) {
+ // Can be visible behind a translucent task.
+ gotTranslucentFullscreen = true;
+ continue;
+ }
+ // Multi-window task that matches parent bounds would occlude other children.
+ return TASK_VISIBILITY_INVISIBLE;
} else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
&& !gotOpaqueSplitScreenPrimary) {
gotSplitScreenStack = true;
@@ -4305,6 +4322,20 @@
// assistant window surfaces in window manager whenever it is visible.
return TASK_VISIBILITY_INVISIBLE;
}
+ if (other.mAdjacentTask != null) {
+ if (adjacentTasks.contains(other.mAdjacentTask)) {
+ if (other.isTranslucent(starting)
+ || other.mAdjacentTask.isTranslucent(starting)) {
+ // Can be visible behind a translucent adjacent tasks.
+ gotTranslucentFullscreen = true;
+ continue;
+ }
+ // Can not be visible behind adjacent tasks.
+ return TASK_VISIBILITY_INVISIBLE;
+ } else {
+ adjacentTasks.add(other);
+ }
+ }
}
if (!shouldBeVisible) {
@@ -5443,9 +5474,13 @@
final Task lastFocusedTask = displayArea.getFocusedRootTask();
displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/);
displayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
+ mAtmService.getTaskChangeNotificationController().notifyTaskMovedToBack(
+ getTaskInfo());
}
if (task != null && task != this) {
positionChildAtBottom(task);
+ mAtmService.getTaskChangeNotificationController().notifyTaskMovedToBack(
+ task.getTaskInfo());
}
return;
}
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 28ebe01..58d508d 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -18,7 +18,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.window.TaskSnapshot;
import android.app.ITaskStackListener;
import android.app.TaskInfo;
import android.content.ComponentName;
@@ -29,6 +28,7 @@
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.window.TaskSnapshot;
import com.android.internal.os.SomeArgs;
@@ -60,6 +60,7 @@
private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 25;
private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 26;
private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 27;
+ private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 28;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -178,6 +179,10 @@
l.onActivityRotation(m.arg1);
};
+ private final TaskStackConsumer mNotifyTaskMovedToBack = (l, m) -> {
+ l.onTaskMovedToBack((RunningTaskInfo) m.obj);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -269,6 +274,9 @@
case NOTIFY_ACTIVITY_ROTATED_MSG:
forAllRemoteListeners(mNotifyOnActivityRotation, msg);
break;
+ case NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyTaskMovedToBack, msg);
+ break;
}
if (msg.obj instanceof SomeArgs) {
((SomeArgs) msg.obj).recycle();
@@ -553,4 +561,13 @@
forAllLocalListeners(mNotifyOnActivityRotation, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify that a task is being moved behind home.
+ */
+ void notifyTaskMovedToBack(TaskInfo ti) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG, ti);
+ forAllLocalListeners(mNotifyTaskMovedToBack, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4156ed6..ad88469 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
}
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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1509ff6..dcab478 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -20,6 +20,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
@@ -279,6 +280,9 @@
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
break;
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+ effects |= setAdjacentRootsHierarchyOp(hop);
+ break;
case HIERARCHY_OP_TYPE_REORDER:
case HIERARCHY_OP_TYPE_REPARENT:
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
@@ -597,6 +601,17 @@
return TRANSACT_EFFECTS_LIFECYCLE;
}
+ private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
+ final Task root1 = WindowContainer.fromBinder(hop.getContainer()).asTask();
+ final Task root2 = WindowContainer.fromBinder(hop.getAdjacentRoot()).asTask();
+ if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
+ throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+ + " organizer root1=" + root1 + " root2=" + root2);
+ }
+ root1.setAdjacentTask(root2);
+ return TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
private void sanitizeWindowContainer(WindowContainer wc) {
if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
throw new RuntimeException("Invalid token in task or displayArea transaction");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b7602fe..5319592 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_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;
@@ -4935,7 +4934,7 @@
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 +4969,6 @@
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7df9016..8a9ec08 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -378,6 +378,8 @@
private static final String CREDENTIAL_MANAGEMENT_APP = "credentialManagementApp";
private static final String NOT_CREDENTIAL_MANAGEMENT_APP = "notCredentialManagementApp";
+ private static final String NULL_STRING_ARRAY = "nullStringArray";
+
// Comprehensive list of delegations.
private static final String DELEGATIONS[] = {
DELEGATION_CERT_INSTALL,
@@ -4187,21 +4189,36 @@
* Calculates strictest (maximum) value for a given password property enforced by admin[s].
*/
@Override
- public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle,
+ boolean deviceWideOnly) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- return getPasswordMinimumMetricsUnchecked(userHandle);
+ return getPasswordMinimumMetricsUnchecked(userHandle, deviceWideOnly);
}
private PasswordMetrics getPasswordMinimumMetricsUnchecked(@UserIdInt int userId) {
+ return getPasswordMinimumMetricsUnchecked(userId, false);
+ }
+
+ private PasswordMetrics getPasswordMinimumMetricsUnchecked(@UserIdInt int userId,
+ boolean deviceWideOnly) {
if (!mHasFeature) {
new PasswordMetrics(CREDENTIAL_TYPE_NONE);
}
Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+ if (deviceWideOnly) {
+ Preconditions.checkArgument(!isManagedProfile(userId));
+ }
ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>();
synchronized (getLockObject()) {
- List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userId);
+ final List<ActiveAdmin> admins;
+ if (deviceWideOnly) {
+ admins = getActiveAdminsForUserAndItsManagedProfilesLocked(userId,
+ /* shouldIncludeProfileAdmins */ (user) -> false);
+ } else {
+ admins = getActiveAdminsForLockscreenPoliciesLocked(userId);
+ }
for (ActiveAdmin admin : admins) {
final boolean isAdminOfUser = userId == admin.getUserHandle().getIdentifier();
// Use the password metrics from the admin in one of three cases:
@@ -4260,24 +4277,23 @@
final int parentUser = getProfileParentId(profileUserId);
enforceUserUnlocked(parentUser);
+ final boolean isSufficient;
synchronized (getLockObject()) {
- // Combine password policies across the user and its profiles. Profile admins are
- // excluded since we only want explicit password requirements, while profile admin
- // requirement are applicable only when the profile has unified challenge.
- List<ActiveAdmin> admins = getActiveAdminsForUserAndItsManagedProfilesLocked(parentUser,
- /* shouldIncludeProfileAdmins */ (user) -> false);
- ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>(admins.size());
- int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
- for (ActiveAdmin admin : admins) {
- adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
- maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
- }
+ int complexity = getAggregatedPasswordComplexityLocked(parentUser, true);
+ PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(parentUser, true);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(parentUser);
- return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
- maxRequiredComplexity, false, metrics).isEmpty();
+ final List<PasswordValidationError> passwordValidationErrors =
+ PasswordMetrics.validatePasswordMetrics(
+ minMetrics, complexity, false, metrics);
+ isSufficient = passwordValidationErrors.isEmpty();
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.IS_ACTIVE_PASSWORD_SUFFICIENT_FOR_DEVICE)
+ .setStrings(mOwners.getProfileOwnerComponent(caller.getUserId()).getPackageName())
+ .write();
+ return isSufficient;
}
@Override
@@ -4380,7 +4396,7 @@
*/
private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
@NonNull PasswordMetrics metrics, @UserIdInt int userId) {
- final int complexity = getEffectivePasswordComplexityRequirementLocked(userId);
+ final int complexity = getAggregatedPasswordComplexityLocked(userId);
PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userId);
final List<PasswordValidationError> passwordValidationErrors =
PasswordMetrics.validatePasswordMetrics(
@@ -4482,9 +4498,20 @@
}
}
- private int getEffectivePasswordComplexityRequirementLocked(@UserIdInt int userHandle) {
+ private int getAggregatedPasswordComplexityLocked(@UserIdInt int userHandle) {
+ return getAggregatedPasswordComplexityLocked(userHandle, false);
+ }
+
+ private int getAggregatedPasswordComplexityLocked(@UserIdInt int userHandle,
+ boolean deviceWideOnly) {
ensureLocked();
- List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
+ final List<ActiveAdmin> admins;
+ if (deviceWideOnly) {
+ admins = getActiveAdminsForUserAndItsManagedProfilesLocked(userHandle,
+ /* shouldIncludeProfileAdmins */ (user) -> false);
+ } else {
+ admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
+ }
int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
for (ActiveAdmin admin : admins) {
maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
@@ -4512,7 +4539,7 @@
}
@Override
- public int getAggregatedPasswordComplexityForUser(int userId) {
+ public int getAggregatedPasswordComplexityForUser(int userId, boolean deviceWideOnly) {
if (!mHasFeature) {
return PASSWORD_COMPLEXITY_NONE;
}
@@ -4521,7 +4548,7 @@
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
- return getEffectivePasswordComplexityRequirementLocked(userId);
+ return getAggregatedPasswordComplexityLocked(userId, deviceWideOnly);
}
}
@@ -4716,7 +4743,7 @@
synchronized (getLockObject()) {
final PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userHandle);
final List<PasswordValidationError> validationErrors;
- final int complexity = getEffectivePasswordComplexityRequirementLocked(userHandle);
+ final int complexity = getAggregatedPasswordComplexityLocked(userHandle);
// TODO: Consider changing validation API to take LockscreenCredential.
if (password.isEmpty()) {
validationErrors = PasswordMetrics.validatePasswordMetrics(
@@ -9559,59 +9586,86 @@
}
@Override
- public boolean setPermittedInputMethods(ComponentName who, List packageList) {
+ public boolean setPermittedInputMethods(ComponentName who, List packageList,
+ boolean calledOnParentInstance) {
if (!mHasFeature) {
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ final int userId = getProfileParentUserIfRequested(
+ caller.getUserId(), calledOnParentInstance);
+ if (calledOnParentInstance) {
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkArgument(packageList == null || packageList.isEmpty(),
+ "Permitted input methods must allow all input methods or only "
+ + "system input methods when called on the parent instance of an "
+ + "organization-owned device");
+ } else {
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ }
if (packageList != null) {
- List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
- .getEnabledInputMethodListAsUser(caller.getUserId());
+ List<InputMethodInfo> enabledImes = mInjector.binderWithCleanCallingIdentity(() ->
+ InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId));
if (enabledImes != null) {
List<String> enabledPackages = new ArrayList<String>();
for (InputMethodInfo ime : enabledImes) {
enabledPackages.add(ime.getPackageName());
}
if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
- caller.getUserId())) {
- Slog.e(LOG_TAG, "Cannot set permitted input methods, "
- + "because it contains already enabled input method.");
+ userId)) {
+ Slog.e(LOG_TAG, "Cannot set permitted input methods, because the list of "
+ + "permitted input methods excludes an already-enabled input method.");
return false;
}
}
}
synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+ final ActiveAdmin admin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParentInstance);
admin.permittedInputMethods = packageList;
saveSettingsLocked(caller.getUserId());
}
- final String[] packageArray =
- packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
+
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PERMITTED_INPUT_METHODS)
.setAdmin(who)
- .setStrings(packageArray)
+ .setStrings(getStringArrayForLogging(packageList, calledOnParentInstance))
.write();
return true;
}
+ private String[] getStringArrayForLogging(List list, boolean calledOnParentInstance) {
+ List<String> stringList = new ArrayList<String>();
+ stringList.add(calledOnParentInstance ? CALLED_FROM_PARENT : NOT_CALLED_FROM_PARENT);
+ if (list == null) {
+ stringList.add(NULL_STRING_ARRAY);
+ } else {
+ stringList.addAll((List<String>) list);
+ }
+ return stringList.toArray(new String[0]);
+ }
+
@Override
- public List getPermittedInputMethods(ComponentName who) {
+ public List getPermittedInputMethods(ComponentName who, boolean calledOnParentInstance) {
if (!mHasFeature) {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ if (calledOnParentInstance) {
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+ } else {
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ }
synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+ final ActiveAdmin admin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParentInstance);
return admin.permittedInputMethods;
}
}
@@ -9624,9 +9678,8 @@
synchronized (getLockObject()) {
List<String> result = null;
// Only device or profile owners can have permitted lists set.
- DevicePolicyData policy = getUserDataUnchecked(caller.getUserId());
- for (int i = 0; i < policy.mAdminList.size(); i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
+ List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(caller.getUserId());
+ for (ActiveAdmin admin: admins) {
List<String> fromAdmin = admin.permittedInputMethods;
if (fromAdmin != null) {
if (result == null) {
@@ -9657,7 +9710,7 @@
@Override
public boolean isInputMethodPermittedByAdmin(ComponentName who, String packageName,
- int userHandle) {
+ int userHandle, boolean calledOnParentInstance) {
if (!mHasFeature) {
return true;
}
@@ -9666,7 +9719,8 @@
enforceSystemCaller("query if an input method is disabled by admin");
synchronized (getLockObject()) {
- ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+ ActiveAdmin admin = getParentOfAdminIfRequired(
+ getActiveAdminUncheckedLocked(who, userHandle), calledOnParentInstance);
if (admin == null) {
return false;
}
@@ -15868,8 +15922,9 @@
Log.i(LOG_TAG,
String.format("Setting Enterprise ID to %s for user %d", organizationId, userId));
+ final String ownerPackage;
synchronized (getLockObject()) {
- ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
// As the caller is the system, it must specify the component name of the profile owner
// as a safety check.
Preconditions.checkCallAuthorization(
@@ -15877,6 +15932,7 @@
String.format("The Profile Owner or Device Owner may only set the Enterprise ID"
+ " on its own user, called on user %d but owner user is %d", userId,
owner.getUserHandle().getIdentifier()));
+ ownerPackage = owner.info.getPackageName();
Preconditions.checkState(
TextUtils.isEmpty(owner.mOrganizationId) || owner.mOrganizationId.equals(
organizationId),
@@ -15894,5 +15950,11 @@
saveSettingsLocked(userId);
});
}
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_ORGANIZATION_ID)
+ .setAdmin(ownerPackage)
+ .setBoolean(isManagedProfile(userId))
+ .write();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 57b3e8d..c613dfb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -323,6 +323,8 @@
"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 @@
}
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/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index d032dbd..8011ec2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1655,7 +1655,7 @@
sService.mConstants.KEEP_WARMING_SERVICES.clear();
final ServiceInfo si = mock(ServiceInfo.class);
si.applicationInfo = mock(ApplicationInfo.class);
- ServiceRecord s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null,
+ ServiceRecord s = spy(new ServiceRecord(sService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
s.startRequested = true;
@@ -1668,7 +1668,7 @@
final ServiceInfo si2 = mock(ServiceInfo.class);
si2.applicationInfo = mock(ApplicationInfo.class);
si2.applicationInfo.uid = MOCKAPP2_UID_OTHER;
- ServiceRecord s2 = spy(new ServiceRecord(sService, null, cn2, cn2, null, 0, null,
+ ServiceRecord s2 = spy(new ServiceRecord(sService, cn2, cn2, null, 0, null,
si2, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s2).getConnections();
s2.startRequested = true;
@@ -1708,7 +1708,7 @@
app.hasShownUi = true;
sService.mConstants.KEEP_WARMING_SERVICES.add(cn);
sService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
- s = spy(new ServiceRecord(sService, null, cn, cn, null, 0, null,
+ s = spy(new ServiceRecord(sService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
s.startRequested = true;
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/MultiRateLimiterTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/MultiRateLimiterTest.java
index df533f3..adeb44b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/MultiRateLimiterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/MultiRateLimiterTest.java
@@ -58,7 +58,7 @@
@Test
public void testSingleRateLimit_belowLimit_isWithinQuota() {
- MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
.addRateLimit(3, Duration.ofSeconds(20))
.build();
@@ -77,7 +77,7 @@
@Test
public void testSingleRateLimit_aboveLimit_isNotWithinQuota() {
- MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
.addRateLimit(3, Duration.ofSeconds(20))
.build();
@@ -97,7 +97,7 @@
@Test
public void testSingleRateLimit_afterGoingAboveQuotaAndWaitingWindow_isBackWithinQuota() {
- MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
.addRateLimit(3, Duration.ofSeconds(20))
.build();
@@ -166,7 +166,7 @@
@Test
public void createSingleRateLimit_testItLimitsOnlyGivenUptc() {
- MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
.addRateLimit(3, Duration.ofSeconds(20))
.build();
@@ -194,4 +194,48 @@
assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_1, TAG)).isTrue();
assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_2, TAG)).isTrue();
}
+
+ @Test
+ public void clearRateLimiterForPackage_afterReachingQuota_quotaIsReset() {
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ .addRateLimit(1, Duration.ofSeconds(100))
+ .build();
+
+ mInjector.mElapsedTime = Duration.ZERO;
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_1, TAG)).isTrue();
+ multiRateLimiter.noteEvent(USER_ID, PACKAGE_NAME_1, TAG);
+
+ mInjector.mElapsedTime = Duration.ofSeconds(1);
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_1, TAG)).isFalse();
+
+ multiRateLimiter.clear(USER_ID, PACKAGE_NAME_1);
+
+ // Quota for that package is reset.
+ mInjector.mElapsedTime = Duration.ofSeconds(1);
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_1, TAG)).isTrue();
+
+ // Quota is enforced again.
+ mInjector.mElapsedTime = Duration.ofSeconds(1);
+ multiRateLimiter.noteEvent(USER_ID, PACKAGE_NAME_1, TAG);
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_1, TAG)).isFalse();
+ }
+
+ @Test
+ public void clearRateLimiterForPackage_doesntAffectOtherPackages() {
+ MultiRateLimiter multiRateLimiter = new MultiRateLimiter.Builder(mContext, mInjector)
+ .addRateLimit(1, Duration.ofSeconds(100))
+ .build();
+
+ mInjector.mElapsedTime = Duration.ZERO;
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_2, TAG)).isTrue();
+ multiRateLimiter.noteEvent(USER_ID, PACKAGE_NAME_2, TAG);
+
+ mInjector.mElapsedTime = Duration.ofSeconds(1);
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_2, TAG)).isFalse();
+
+ multiRateLimiter.clear(USER_ID, PACKAGE_NAME_1);
+
+ // Doesn't affect the other package.
+ assertThat(multiRateLimiter.isWithinQuota(USER_ID, PACKAGE_NAME_2, TAG)).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index b360ae8..e119d52 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -21,12 +21,12 @@
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
-import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.util.DebugUtils.valueToString;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -69,7 +69,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
@@ -105,7 +104,6 @@
* atest FrameworksServicesTests:ActivityManagerServiceTest
*/
@SmallTest
-@FlakyTest(bugId = 113616538)
public class ActivityManagerServiceTest {
private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
@@ -199,8 +197,8 @@
// Uid state is not moving from background to foreground or vice versa.
verifySeqCounterAndInteractions(uidRec,
- PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
- PROCESS_STATE_IMPORTANT_FOREGROUND, // curState
+ PROCESS_STATE_TRANSIENT_BACKGROUND, // prevState
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
42, // expectedGlobalCounter
1, // exptectedCurProcStateSeq
NETWORK_STATE_NO_CHANGE, // expectedBlockState
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 6366155..b2a860d 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
@@ -27,6 +27,11 @@
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+
+import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.proto.DocumentProto;
@@ -55,11 +60,30 @@
public class AppSearchImplTest {
@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 {
- mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+ 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,
+ /*globalQuerierPackage=*/ mContext.getPackageName());
+ mGlobalQuerierUid =
+ mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
}
// TODO(b/175430168) add test to verify reset is working properly.
@@ -460,7 +484,8 @@
// 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 +530,10 @@
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 +545,38 @@
}
@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,7 +700,9 @@
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, mContext.getPackageName(), /*callerUid=*/ 0);
assertThat(searchResultPage.getResults()).isEmpty();
}
@@ -685,7 +747,9 @@
// 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);
+ 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
@@ -738,7 +802,9 @@
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package1")
.build();
- SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
+ SearchResultPage searchResultPage =
+ mAppSearchImpl.globalQuery(
+ "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1);
@@ -748,7 +814,9 @@
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package2")
.build();
- searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
+ searchResultPage =
+ mAppSearchImpl.globalQuery(
+ "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
}
@@ -794,7 +862,8 @@
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
- .setSchemaType("package$database1/Email"))
+ .setSchemaType("package$database1/Email")
+ .setVersion(0))
.build();
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
@@ -806,9 +875,16 @@
@Test
public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
- PackageIdentifier package1 =
- new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
+ // 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.mockGetPackageUid(packageNameFoo, uidFoo);
+ mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+
+ // Set schema1
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
@@ -816,19 +892,21 @@
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
- "schema1", ImmutableList.of(package1)),
+ "schema1",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false);
// "schema1" is platform hidden now and package visible to package1
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema1", mGlobalQuerierUid))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
.isTrue();
// Add a new schema, and include the already-existing "schema1"
@@ -840,31 +918,34 @@
new AppSearchSchema.Builder("schema2").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
- "schema1", ImmutableList.of(package1)),
+ "schema1",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false);
// Check that "schema1" still has the same visibility settings
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema1", mGlobalQuerierUid))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
.isTrue();
// "schema2" has default visibility settings
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema2"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema2", mGlobalQuerierUid))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema2", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema2", uidFoo))
.isFalse();
}
@@ -891,10 +972,12 @@
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.
@@ -935,7 +1018,8 @@
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
- .setSchemaType("package$database1/Email"))
+ .setSchemaType("package$database1/Email")
+ .setVersion(0))
.build();
expectedTypes = new ArrayList<>();
@@ -977,16 +1061,20 @@
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 +1100,16 @@
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.
@@ -1031,8 +1122,14 @@
@Test
public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
- PackageIdentifier package1 =
- new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
+ // 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.mockGetPackageUid(packageNameFoo, uidFoo);
+ mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -1041,19 +1138,21 @@
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
- "schema1", ImmutableList.of(package1)),
+ "schema1",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false);
// "schema1" is platform hidden now and package accessible
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema1", mGlobalQuerierUid))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
.isTrue();
// Remove "schema1" by force overriding
@@ -1069,12 +1168,13 @@
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema1", mGlobalQuerierUid))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
.isFalse();
// Add "schema1" back, it gets default visibility settings which means it's not platform
@@ -1089,12 +1189,13 @@
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "schema1", mGlobalQuerierUid))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
.isFalse();
}
@@ -1111,7 +1212,8 @@
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "Schema"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "Schema", mGlobalQuerierUid))
.isTrue();
}
@@ -1128,15 +1230,13 @@
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPlatformSurfaceable(prefix, prefix + "Schema"))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "Schema", mGlobalQuerierUid))
.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",
@@ -1148,14 +1248,21 @@
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "Schema", package1))
+ .isSchemaSearchableByCaller(
+ prefix, prefix + "Schema", /*callerUid=*/ 42))
.isFalse();
}
@Test
public void testSetSchema_packageAccessible() throws Exception {
- PackageIdentifier package1 =
- new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
+ // 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.mockGetPackageUid(packageNameFoo, uidFoo);
+ mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -1163,12 +1270,14 @@
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
- /*schemasPackageAccessible=*/ ImmutableMap.of("Schema", ImmutableList.of(package1)),
+ /*schemasPackageAccessible=*/ ImmutableMap.of(
+ "Schema",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
- .isSchemaPackageAccessible(prefix, prefix + "Schema", package1))
+ .isSchemaSearchableByCaller(prefix, prefix + "Schema", uidFoo))
.isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/MockPackageManager.java
new file mode 100644
index 0000000..c2a8243
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/MockPackageManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Mock to help test package name, UID, and certificate verification
+ *
+ * @hide
+ */
+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.getPackageUid(eq(packageName), /*flags=*/ 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 mockGetPackageUid(String packageName, int uid) {
+ try {
+ when(mMockPackageManager.getPackageUid(eq(packageName), /*flags=*/ anyInt()))
+ .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/external/localstorage/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java
index e491ac3..da3e999 100644
--- 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
@@ -19,6 +19,11 @@
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;
@@ -34,12 +39,32 @@
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 {
- mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+ 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,
+ /*globalQuerierPackage=*/ mContext.getPackageName());
+ mGlobalQuerierUid =
+ mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
+
mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
}
@@ -80,9 +105,13 @@
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema2"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema1", mGlobalQuerierUid))
.isFalse();
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema2", mGlobalQuerierUid))
.isFalse();
// New .setVisibility() call completely overrides previous visibility settings. So
@@ -92,92 +121,198 @@
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema3"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema1", mGlobalQuerierUid))
.isFalse();
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema2", mGlobalQuerierUid))
.isTrue();
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema3", mGlobalQuerierUid))
.isFalse();
+ // Everything defaults to visible again.
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ Collections.emptyMap());
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema1", mGlobalQuerierUid))
.isTrue();
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema2", mGlobalQuerierUid))
.isTrue();
- assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3"))
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schema3", mGlobalQuerierUid))
.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});
+ 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,
+ /*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/schema1", ImmutableList.of(package1),
- "prefix/schema2", ImmutableList.of(package2)));
- assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1))
+ "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.mockGetPackageUid(packageNameFoo, 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.mockGetPackageUid(packageNameFoo, 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.mockGetPackageUid(packageNameFoo, uidFoo);
+ mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schemaFoo", uidFoo))
.isTrue();
- assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2))
+
+ mMockPackageManager.mockGetPackageUid(packageNameBar, uidBar);
+ mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schemaBar", uidBar))
.isTrue();
// New .setVisibility() call completely overrides previous visibility settings. So
- // "schema2" isn't preserved.
+ // "schemaBar" settings aren'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))
+ "prefix/schemaFoo",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
+
+ mMockPackageManager.mockGetPackageUid(packageNameFoo, uidFoo);
+ mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ "prefix", "prefix/schemaFoo", uidFoo))
.isTrue();
+ mMockPackageManager.mockGetPackageUid(packageNameBar, 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=*/ 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))
+ /*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 {
- PackageIdentifier package1 =
- new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
- PackageIdentifier package2 =
- new PackageIdentifier("package2", /*sha256Certificate=*/ new byte[] {100});
+ // Values for a "foo" client
+ String packageNameFoo = "packageFoo";
+ byte[] sha256CertFoo = new byte[] {10};
+ int uidFoo = 1;
mVisibilityStore.setVisibility(
/*prefix=*/ "",
- /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"),
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*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))
+ "schema",
+ ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
+
+ assertThat(
+ mVisibilityStore.isSchemaSearchableByCaller(
+ /*prefix=*/ "", "schema", mGlobalQuerierUid))
.isTrue();
- assertThat(mVisibilityStore.isSchemaPackageAccessible(/*prefix=*/ "", "schema2", package2))
+
+ mMockPackageManager.mockGetPackageUid(packageNameFoo, 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/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
index 88edcb8..dc225f1 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 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 @@
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 @@
.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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a455ba9..065a2f3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -107,6 +107,7 @@
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -2333,6 +2334,32 @@
}
@Test
+ public void testSetPermittedInputMethodsWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ String packageName = "com.google.pkg.one";
+ setupProfileOwner();
+ configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+ // Allow all input methods
+ parentDpm.setPermittedInputMethods(admin1, null);
+
+ assertThat(parentDpm.getPermittedInputMethods(admin1)).isNull();
+
+ // Allow only system input methods
+ parentDpm.setPermittedInputMethods(admin1, new ArrayList<>());
+
+ assertThat(parentDpm.getPermittedInputMethods(admin1)).isEmpty();
+
+ // Don't allow specific third party input methods
+ final List<String> inputMethods = Collections.singletonList(packageName);
+
+ assertExpectException(IllegalArgumentException.class, /* messageRegex= */ "Permitted "
+ + "input methods must allow all input methods or only system input methods "
+ + "when called on the parent instance of an organization-owned device",
+ () -> parentDpm.setPermittedInputMethods(admin1, inputMethods));
+ }
+
+ @Test
public void testSetKeyguardDisabledFeaturesWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -5133,6 +5160,54 @@
}
@Test
+ public void testGetAggregatedPasswordComplexity_IgnoreProfileRequirement()
+ throws Exception {
+ final int managedProfileUserId = CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+ parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+
+ assertThat(dpms.getAggregatedPasswordComplexityForUser(UserHandle.USER_SYSTEM, true))
+ .isEqualTo(PASSWORD_COMPLEXITY_LOW);
+ assertThat(dpms.getAggregatedPasswordComplexityForUser(UserHandle.USER_SYSTEM, false))
+ .isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ }
+
+ @Test
+ public void testGetAggregatedPasswordMetrics_IgnoreProfileRequirement()
+ throws Exception {
+ final int managedProfileUserId = CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+ dpm.setPasswordMinimumLength(admin1, 8);
+ dpm.setPasswordMinimumLetters(admin1, 1);
+ dpm.setPasswordMinimumNumeric(admin1, 2);
+ dpm.setPasswordMinimumSymbols(admin1, 3);
+
+ parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+ PasswordMetrics deviceMetrics =
+ dpms.getPasswordMinimumMetrics(UserHandle.USER_SYSTEM, true);
+ assertThat(deviceMetrics.credType).isEqualTo(LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+
+ PasswordMetrics allMetrics =
+ dpms.getPasswordMinimumMetrics(UserHandle.USER_SYSTEM, false);
+ assertThat(allMetrics.credType).isEqualTo(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+ assertThat(allMetrics.length).isEqualTo(8);
+ assertThat(allMetrics.letters).isEqualTo(1);
+ assertThat(allMetrics.numeric).isEqualTo(2);
+ assertThat(allMetrics.symbols).isEqualTo(3);
+ }
+
+ @Test
public void testCanSetPasswordRequirementOnParentPreS() throws Exception {
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 6e4d994..44418ce 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -105,6 +105,10 @@
}
@Override
+ protected void writeStringSystemProperty(String key, String value) {
+ }
+
+ @Override
Looper getServiceLooper() {
return mTestLooper.getLooper();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index bbe1156..d454d87 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -106,6 +106,10 @@
}
@Override
+ protected void writeStringSystemProperty(String key, String value) {
+ }
+
+ @Override
Looper getServiceLooper() {
return mTestLooper.getLooper();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index aeeca1a..8e37f94 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -95,6 +95,15 @@
+ " </allowed-values>"
+ " <default-value int-value=\"1\" />"
+ " </setting>"
+ + " <setting name=\"volume_control_enabled\""
+ + " value-type=\"int\""
+ + " user-configurable=\"true\">"
+ + " <allowed-values>"
+ + " <value int-value=\"0\" />"
+ + " <value int-value=\"1\" />"
+ + " </allowed-values>"
+ + " <default-value int-value=\"1\" />"
+ + " </setting>"
+ "</cec-settings>";
FakeHdmiCecConfig(@NonNull Context context) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 6bb68da..95a0a74 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -195,8 +195,9 @@
}
};
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
-
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mMyLooper = mTestLooper.getLooper();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@@ -710,7 +711,8 @@
public void giveAudioStatus_volumeEnabled() {
mMusicVolume = 50;
mMusicMaxVolume = 100;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
int volume = mHdmiControlService.getAudioManager()
@@ -740,7 +742,8 @@
public void giveAudioStatus_volumeDisabled() {
mMusicVolume = 50;
mMusicMaxVolume = 100;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
int volume = mHdmiControlService.getAudioManager()
@@ -770,7 +773,8 @@
public void reportAudioStatus_volumeEnabled() {
mMusicVolume = 50;
mMusicMaxVolume = 100;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
int volume = mHdmiControlService.getAudioManager()
@@ -794,7 +798,8 @@
public void reportAudioStatus_volumeDisabled() {
mMusicVolume = 50;
mMusicMaxVolume = 100;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
int volume = mHdmiControlService.getAudioManager()
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 7aea4ff..bc80876 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -862,7 +862,8 @@
@Test
public void sendVolumeKeyEvent_up_volumeEnabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
mTestLooper.dispatchAll();
@@ -879,7 +880,8 @@
@Test
public void sendVolumeKeyEvent_down_volumeEnabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
mTestLooper.dispatchAll();
@@ -896,7 +898,8 @@
@Test
public void sendVolumeKeyEvent_mute_volumeEnabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false);
mTestLooper.dispatchAll();
@@ -913,7 +916,8 @@
@Test
public void sendVolumeKeyEvent_up_volumeDisabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
mTestLooper.dispatchAll();
@@ -930,7 +934,8 @@
@Test
public void sendVolumeKeyEvent_down_volumeDisabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
mTestLooper.dispatchAll();
@@ -947,7 +952,8 @@
@Test
public void sendVolumeKeyEvent_mute_volumeDisabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true);
mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false);
mTestLooper.dispatchAll();
@@ -964,7 +970,8 @@
@Test
public void sendVolumeKeyEvent_toTv_activeSource() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiControlService.setSystemAudioActivated(false);
mHdmiControlService.setActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress,
"HdmiCecLocalDevicePlaybackTest");
@@ -984,7 +991,8 @@
@Test
public void sendVolumeKeyEvent_toAudio_activeSource() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiControlService.setSystemAudioActivated(true);
mHdmiControlService.setActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress,
"HdmiCecLocalDevicePlaybackTest");
@@ -1004,7 +1012,8 @@
@Test
public void sendVolumeKeyEvent_toTv_inactiveSource() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiControlService.setSystemAudioActivated(false);
mHdmiControlService.setActiveSource(ADDR_TV, 0x0000, "HdmiCecLocalDevicePlaybackTest");
@@ -1023,7 +1032,8 @@
@Test
public void sendVolumeKeyEvent_toAudio_inactiveSource() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiControlService.setSystemAudioActivated(true);
mHdmiControlService.setActiveSource(ADDR_TV, 0x0000, "HdmiCecLocalDevicePlaybackTest");
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index eb2f960..0717112 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -32,6 +32,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.os.test.TestLooper;
@@ -124,8 +125,13 @@
@Before
public void SetUp() {
+
+ Context context = InstrumentationRegistry.getTargetContext();
+
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context);
+
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ new HdmiControlService(context) {
@Override
boolean isControlEnabled() {
return isControlEnabled;
@@ -157,6 +163,11 @@
void wakeUp() {
mWakeupMessageReceived = true;
}
+
+ @Override
+ protected HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
};
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
mNativeWrapper = new FakeNativeWrapper();
@@ -244,7 +255,8 @@
@Test
public void handleUserControlPressed_volumeUp() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
@@ -254,7 +266,8 @@
@Test
public void handleUserControlPressed_volumeDown() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
@@ -264,7 +277,8 @@
@Test
public void handleUserControlPressed_volumeMute() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_MUTE));
@@ -274,7 +288,8 @@
@Test
public void handleUserControlPressed_volumeUp_disabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
@@ -284,7 +299,8 @@
@Test
public void handleUserControlPressed_volumeDown_disabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
@@ -294,7 +310,8 @@
@Test
public void handleUserControlPressed_volumeMute_disabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
boolean result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_MUTE));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
index c212bf8..70718f7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
@@ -136,6 +136,8 @@
// Some tests expect no logical addresses being allocated at the beginning of the test.
setHdmiControlEnabled(false);
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContext);
+
mHdmiControlService =
new HdmiControlService(mContext) {
@Override
@@ -158,6 +160,11 @@
boolean isPowerStandby() {
return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
}
+
+ @Override
+ protected HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
};
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 9152e1e..be584d7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -154,6 +154,10 @@
}
@Override
+ protected void writeStringSystemProperty(String key, String value) {
+ }
+
+ @Override
protected HdmiCecConfig getHdmiCecConfig() {
return hdmiCecConfig;
}
@@ -251,7 +255,7 @@
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mNativeWrapper.clearResultMessages();
assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
@@ -271,7 +275,7 @@
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mNativeWrapper.clearResultMessages();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -291,136 +295,175 @@
@Test
public void setAndGetCecVolumeControlEnabled_isApi() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue();
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void setAndGetCecVolumeControlEnabled_changesSetting() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
- assertThat(mHdmiControlService.readBooleanSetting(
- Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true)).isFalse();
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ assertThat(mHdmiControlService.readIntSetting(
+ Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
- assertThat(mHdmiControlService.readBooleanSetting(
- Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true)).isTrue();
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ assertThat(mHdmiControlService.readIntSetting(
+ Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(true);
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(false);
- assertThat(mHdmiControlService.readBooleanSetting(
- Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true)).isTrue();
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(true);
- assertThat(mHdmiControlService.readBooleanSetting(
- Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true)).isTrue();
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() {
- boolean volumeControlEnabled = true;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(volumeControlEnabled);
+ int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
- mHdmiControlService.setControlEnabled(false);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setControlEnabled(true);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isEqualTo(
- volumeControlEnabled);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(volumeControlEnabled);
}
@Test
public void disableAndReenableCec_volumeControlReturnsToOriginalValue_disabled() {
- boolean volumeControlEnabled = false;
- mHdmiControlService.setHdmiCecVolumeControlEnabled(volumeControlEnabled);
+ int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_DISABLED;
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled);
- mHdmiControlService.setControlEnabled(false);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
+ volumeControlEnabled);
- mHdmiControlService.setControlEnabled(true);
- assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isEqualTo(
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
volumeControlEnabled);
}
@Test
public void disableAndReenableCec_volumeControlFeatureListenersNotified() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
- mHdmiControlService.setControlEnabled(false);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
assertThat(callback.mCallbackReceived).isTrue();
- assertThat(callback.mVolumeControlEnabled).isFalse();
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setControlEnabled(true);
- assertThat(callback.mVolumeControlEnabled).isTrue();
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
- assertThat(callback.mVolumeControlEnabled).isTrue();
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
- assertThat(callback.mVolumeControlEnabled).isFalse();
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
- assertThat(callback.mVolumeControlEnabled).isTrue();
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback);
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
- assertThat(callback.mVolumeControlEnabled).isFalse();
+ assertThat(callback.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() {
- mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback();
VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback();
@@ -428,13 +471,16 @@
mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2);
- mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
assertThat(callback1.mCallbackReceived).isTrue();
assertThat(callback2.mCallbackReceived).isTrue();
- assertThat(callback1.mVolumeControlEnabled).isTrue();
- assertThat(callback2.mVolumeControlEnabled).isTrue();
+ assertThat(callback1.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ assertThat(callback2.mVolumeControlEnabled).isEqualTo(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
@@ -443,7 +489,7 @@
Settings.Global.putString(mContextSpy.getContentResolver(),
Settings.Global.HDMI_CEC_VERSION,
null);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
}
@@ -453,7 +499,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
}
@@ -463,7 +509,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_2_0);
}
@@ -473,14 +519,14 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_2_0);
}
@@ -490,7 +536,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mTestLooper.dispatchAll();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -508,7 +554,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -530,7 +576,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -547,7 +593,7 @@
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(true);
+ mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -579,10 +625,10 @@
private static class VolumeControlFeatureCallback extends
IHdmiCecVolumeControlFeatureListener.Stub {
boolean mCallbackReceived = false;
- boolean mVolumeControlEnabled = false;
+ int mVolumeControlEnabled = -1;
@Override
- public void onHdmiCecVolumeControlFeature(boolean enabled) throws RemoteException {
+ public void onHdmiCecVolumeControlFeature(int enabled) throws RemoteException {
this.mCallbackReceived = true;
this.mVolumeControlEnabled = enabled;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index f80b573..f9160ab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -138,6 +138,10 @@
}
@Override
+ protected void writeStringSystemProperty(String key, String value) {
+ }
+
+ @Override
void wakeUp() {}
@Override
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 0000000..0471e29
--- /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 0000000..8ac6dfb
--- /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/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6f584ee..ef96a2d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1064,6 +1064,21 @@
}
/**
+ * Verify that finish bottom activity from a task won't boost it to top.
+ */
+ @Test
+ public void testFinishBottomActivityIfPossible_noZBoost() {
+ final ActivityRecord bottomActivity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(bottomActivity.getTask()).build();
+ topActivity.mVisibleRequested = true;
+ // simulating bottomActivity as a trampoline activity.
+ bottomActivity.setState(RESUMED, "test");
+ bottomActivity.finishIfPossible("test", false);
+ assertFalse(bottomActivity.mNeedsZBoost);
+ }
+
+ /**
* Verify that complete finish request for visible activity must be delayed before the next one
* becomes visible.
*/
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 717040a..089a948 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3014,6 +3014,26 @@
public void onCallAudioStateChanged(CallAudioState state) {}
/**
+ * Inform this Connection when it will or will not be tracked by an {@link InCallService} which
+ * can provide an InCall UI.
+ * This is primarily intended for use by Connections reported by self-managed
+ * {@link ConnectionService} which typically maintain their own UI.
+ *
+ * @param isUsingAlternativeUi Indicates whether an InCallService that can provide InCall UI is
+ * currently tracking the self-managed call.
+ */
+ public void onUsingAlternativeUi(boolean isUsingAlternativeUi) {}
+
+ /**
+ * Inform this Conenection when it will or will not be tracked by an non-UI
+ * {@link InCallService}.
+ *
+ * @param isTracked Indicates whether an non-UI InCallService is currently tracking the
+ * self-managed call.
+ */
+ public void onTrackedByNonUiService(boolean isTracked) {}
+
+ /**
* Notifies this Connection of an internal state change. This method is called after the
* state is changed.
*
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index f86f9d5..5c75a2f 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -137,6 +137,8 @@
private static final String SESSION_HOLD = "CS.h";
private static final String SESSION_UNHOLD = "CS.u";
private static final String SESSION_CALL_AUDIO_SC = "CS.cASC";
+ private static final String SESSION_USING_ALTERNATIVE_UI = "CS.uAU";
+ private static final String SESSION_TRACKED_BY_NON_UI_SERVICE = "CS.tBNUS";
private static final String SESSION_PLAY_DTMF = "CS.pDT";
private static final String SESSION_STOP_DTMF = "CS.sDT";
private static final String SESSION_CONFERENCE = "CS.c";
@@ -202,6 +204,8 @@
private static final int MSG_EXPLICIT_CALL_TRANSFER = 40;
private static final int MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE = 41;
private static final int MSG_ON_CALL_FILTERING_COMPLETED = 42;
+ private static final int MSG_ON_USING_ALTERNATIVE_UI = 43;
+ private static final int MSG_ON_TRACKED_BY_NON_UI_SERVICE = 44;
private static Connection sNullConnection;
@@ -586,6 +590,36 @@
}
@Override
+ public void onUsingAlternativeUi(String callId, boolean usingAlternativeUiShowing,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_USING_ALTERNATIVE_UI);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = usingAlternativeUiShowing;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_USING_ALTERNATIVE_UI, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onTrackedByNonUiService(String callId, boolean isTracked,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_TRACKED_BY_NON_UI_SERVICE);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = isTracked;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_TRACKED_BY_NON_UI_SERVICE, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void playDtmfTone(String callId, char digit, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_PLAY_DTMF);
try {
@@ -1244,6 +1278,34 @@
}
break;
}
+ case MSG_ON_USING_ALTERNATIVE_UI: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_USING_ALTERNATIVE_UI);
+ try {
+ String callId = (String) args.arg1;
+ boolean isUsingAlternativeUi = (boolean) args.arg2;
+ onUsingAlternativeUi(callId, isUsingAlternativeUi);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_ON_TRACKED_BY_NON_UI_SERVICE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_TRACKED_BY_NON_UI_SERVICE);
+ try {
+ String callId = (String) args.arg1;
+ boolean isTracked = (boolean) args.arg2;
+ onTrackedByNonUiService(callId, isTracked);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_PLAY_DTMF_TONE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -1961,10 +2023,12 @@
request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
- Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
- "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
- callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
- isHandover);
+ boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
+ PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+ Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
+ + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
+ + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
+ isUnknown, isLegacyHandover, isHandover, addSelfManaged);
Connection connection = null;
if (isHandover) {
@@ -2239,6 +2303,22 @@
}
}
+ private void onUsingAlternativeUi(String callId, boolean isUsingAlternativeUi) {
+ Log.i(this, "onUsingAlternativeUi %s %s", callId, isUsingAlternativeUi);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onUsingAlternativeUi")
+ .onUsingAlternativeUi(isUsingAlternativeUi);
+ }
+ }
+
+ private void onTrackedByNonUiService(String callId, boolean isTracked) {
+ Log.i(this, "onTrackedByNonUiService %s %s", callId, isTracked);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onTrackedByNonUiService")
+ .onTrackedByNonUiService(isTracked);
+ }
+ }
+
private void playDtmfTone(String callId, char digit) {
Log.i(this, "playDtmfTone %s %c", callId, digit);
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 835ecaa..579b33e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -188,6 +188,15 @@
"android.telecom.extra.SKIP_CALL_FILTERING";
/**
+ * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
+ * indicates whether a Self-managed {@link PhoneAccount} want to expose its calls to all
+ * {@link InCallService} which declares the metadata
+ * {@link TelecomManager#METADATA_INCLUDE_SELF_MANAGED_CALLS}.
+ */
+ public static final String EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE =
+ "android.telecom.extra.ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE";
+
+ /**
* Flag indicating that this {@code PhoneAccount} can act as a connection manager for
* other connections. The {@link ConnectionService} associated with this {@code PhoneAccount}
* will be allowed to manage phone calls including using its own proprietary phone-call
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 92264be..301c2bb 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -139,4 +139,9 @@
int error, in Session.Info sessionInfo);
void handoverComplete(String callId, in Session.Info sessionInfo);
+
+ void onUsingAlternativeUi(String callId, boolean isUsingAlternativeUi,
+ in Session.Info sessionInfo);
+
+ void onTrackedByNonUiService(String callId, boolean isTracked, in Session.Info sessionInfo);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index daa3d1f0..8041fa9 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4638,6 +4638,18 @@
"use_lower_mtu_value_if_both_received";
/**
+ * Determines the default RTT mode.
+ *
+ * Upon first boot, when the user has not yet set a value for their preferred RTT mode,
+ * the value of this config will be sent to the IMS stack. Valid values are the same as for
+ * {@link Settings.Secure#RTT_CALLING_MODE}.
+ *
+ * @hide
+ */
+ public static final String KEY_DEFAULT_RTT_MODE_INT =
+ "default_rtt_mode_int";
+
+ /**
* Indicates if auto-configuration server is used for the RCS config
* Reference: GSMA RCC.14
*/
@@ -5201,6 +5213,7 @@
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.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
}
/**
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index e06dcdb..6c013df 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -384,7 +384,6 @@
* 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 @@
* <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 @@
* <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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* <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/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 2ebb9c1..94950dc 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
@@ -16,11 +16,11 @@
package com.android.tests.rollback.host;
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.tests.rollback.host.WatchdogEventLogger.Subject.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -62,9 +62,9 @@
* For example, <code>runPhase("testApkOnlyEnableRollback");</code>
*/
private void runPhase(String phase) throws Exception {
- assertTrue(runDeviceTests("com.android.tests.rollback",
+ assertThat(runDeviceTests("com.android.tests.rollback",
"com.android.tests.rollback.StagedRollbackTest",
- phase));
+ phase)).isTrue();
}
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
@@ -150,17 +150,15 @@
// Trigger rollback and wait for reboot to happen
runPhase("testBadApkOnly_Phase3");
- assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(2)));
+ assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(2))).isTrue();
getDevice().waitForDeviceAvailable();
runPhase("testBadApkOnly_Phase4");
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_INITIATE, null,
- REASON_APP_CRASH, TESTAPP_A));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_SUCCESS, null, null, null));
+ assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
+ assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
}
@Test
@@ -183,17 +181,15 @@
// 3. Staged rollback session becomes ready.
// 4. Device actually reboots.
// So we give a generous timeout here.
- assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue();
getDevice().waitForDeviceAvailable();
// verify rollback committed
runPhase("testNativeWatchdogTriggersRollback_Phase3");
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_INITIATE, null,
- REASON_NATIVE_CRASH, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_SUCCESS, null, null, null));
+ assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
}
@Test
@@ -223,17 +219,15 @@
// 3. Staged rollback session becomes ready.
// 4. Device actually reboots.
// So we give a generous timeout here.
- assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue();
getDevice().waitForDeviceAvailable();
// verify all available rollbacks have been committed
runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_INITIATE, null,
- REASON_NATIVE_CRASH, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_SUCCESS, null, null, null));
+ assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
}
/**
@@ -306,16 +300,14 @@
// Verify apex was installed and then crash the apk
runPhase("testRollbackApexWithApkCrashing_Phase2");
// Wait for crash to trigger rollback
- assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ assertThat(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))).isTrue();
getDevice().waitForDeviceAvailable();
// Verify rollback occurred due to crash of apk-in-apex
runPhase("testRollbackApexWithApkCrashing_Phase3");
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_INITIATE, null,
- REASON_APP_CRASH, TESTAPP_A));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- assertTrue(mLogger.watchdogEventOccurred(ROLLBACK_SUCCESS, null, null, null));
+ assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
+ assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
+ assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
}
/**
@@ -356,10 +348,10 @@
// Verify that old files have been restored and new files are gone
runAsRoot(() -> {
- assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1));
- assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2));
- assertNull(getDevice().pullFile(newFilePath3));
- assertNull(getDevice().pullFile(newFilePath4));
+ 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();
});
// Verify snapshots are deleted after restoration
@@ -411,10 +403,10 @@
// Verify that old files have been restored and new files are gone
runAsRoot(() -> {
- assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1));
- assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2));
- assertNull(getDevice().pullFile(newFilePath3));
- assertNull(getDevice().pullFile(newFilePath4));
+ 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();
});
// Verify snapshots are deleted after restoration
@@ -464,10 +456,10 @@
// Verify that old files have been restored and new files are gone
runAsRoot(() -> {
- assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1));
- assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2));
- assertNull(getDevice().pullFile(newFilePath3));
- assertNull(getDevice().pullFile(newFilePath4));
+ 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();
});
// Verify snapshots are deleted after restoration
@@ -515,10 +507,10 @@
// Verify that old files have been restored and new files are gone
runAsRoot(() -> {
- assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1));
- assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2));
- assertNull(getDevice().pullFile(newFilePath3));
- assertNull(getDevice().pullFile(newFilePath4));
+ 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();
});
}
@@ -549,7 +541,7 @@
runPhase("testCleanUp");
runAsRoot(() -> {
for (String dir : after) {
- assertNull(getDevice().getFileEntry(dir));
+ assertThat(getDevice().getFileEntry(dir)).isNull();
}
});
}
@@ -561,7 +553,7 @@
try {
getDevice().enableAdbRoot();
getDevice().remountSystemWritable();
- assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ assertThat(getDevice().pushFile(apex, "/system/apex/" + fileName)).isTrue();
} finally {
getDevice().disableAdbRoot();
}
@@ -607,8 +599,9 @@
try {
getDevice().enableAdbRoot();
IFileEntry file = getDevice().getFileEntry(path);
- assertTrue("Not a directory: " + path, file.isDirectory());
- assertTrue("Directory not empty: " + path, file.getChildren(false).isEmpty());
+ assertWithMessage("Not a directory: " + path).that(file.isDirectory()).isTrue();
+ assertWithMessage("Directory not empty: " + path)
+ .that(file.getChildren(false)).isEmpty();
} catch (DeviceNotAvailableException e) {
fail("Can't access directory: " + path);
} finally {
diff --git a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
index 6b0d1f8..8c16079 100644
--- a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
+++ b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
@@ -17,6 +17,8 @@
package com.android.tests.rollback.host;
import com.android.tradefed.device.ITestDevice;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Truth;
import static com.google.common.truth.Truth.assertThat;
@@ -76,4 +78,30 @@
&& matchProperty(type, "rollbackReason", rollbackReason)
&& matchProperty(type, "failedPackageName", failedPackageName);
}
+
+ static class Subject extends com.google.common.truth.Subject {
+ private final WatchdogEventLogger mActual;
+
+ private Subject(FailureMetadata failureMetadata, WatchdogEventLogger subject) {
+ super(failureMetadata, subject);
+ mActual = subject;
+ }
+
+ private static com.google.common.truth.Subject.Factory<Subject,
+ WatchdogEventLogger> loggers() {
+ return Subject::new;
+ }
+
+ static Subject assertThat(WatchdogEventLogger actual) {
+ return Truth.assertAbout(loggers()).that(actual);
+ }
+
+ void eventOccurred(String type, String logPackage, String rollbackReason,
+ String failedPackageName) throws Exception {
+ check("watchdogEventOccurred(type=%s, logPackage=%s, rollbackReason=%s, "
+ + "failedPackageName=%s)", type, logPackage, rollbackReason, failedPackageName)
+ .that(mActual.watchdogEventOccurred(type, logPackage, rollbackReason,
+ failedPackageName)).isTrue();
+ }
+ }
}