diff options
235 files changed, 6689 insertions, 1882 deletions
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index e08d22ce194c..47cf17cb1d4c 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -8,6 +8,14 @@ package android.app.appsearch { method public boolean isSuccess(); } + public static final class AppSearchBatchResult.Builder<KeyType, ValueType> { + ctor public AppSearchBatchResult.Builder(); + method @NonNull public android.app.appsearch.AppSearchBatchResult<KeyType,ValueType> build(); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setFailure(@NonNull KeyType, int, @Nullable String); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setResult(@NonNull KeyType, @NonNull android.app.appsearch.AppSearchResult<ValueType>); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setSuccess(@NonNull KeyType, @Nullable ValueType); + } + public class AppSearchManager { method public void createGlobalSearchSession(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GlobalSearchSession>>); method public void createSearchSession(@NonNull android.app.appsearch.AppSearchManager.SearchContext, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.AppSearchSession>>); @@ -29,6 +37,8 @@ package android.app.appsearch { method public int getResultCode(); method @Nullable public ValueType getResultValue(); method public boolean isSuccess(); + method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newFailedResult(int, @Nullable String); + method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newSuccessfulResult(@Nullable ValueType); field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2 field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3 field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7 @@ -36,13 +46,14 @@ package android.app.appsearch { field public static final int RESULT_NOT_FOUND = 6; // 0x6 field public static final int RESULT_OK = 0; // 0x0 field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5 + field public static final int RESULT_SECURITY_ERROR = 8; // 0x8 field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1 } 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(); + method @Deprecated @IntRange(from=0) public int getVersion(); } public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -58,7 +69,7 @@ package android.app.appsearch { 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); + method @Deprecated @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int); } public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -130,13 +141,16 @@ package android.app.appsearch { public final class AppSearchSession implements java.io.Closeable { method public void close(); method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>); - method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>); + method public void getNamespaces(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<java.lang.String>>>); + method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GetSchemaResponse>>); + method public void getStorageInfo(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.StorageInfo>>); method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); - method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); + method @Deprecated public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); + method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); } public interface BatchResultCallback<KeyType, ValueType> { @@ -203,16 +217,29 @@ package android.app.appsearch { method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String); } + public class GetSchemaResponse extends java.util.HashSet<android.app.appsearch.AppSearchSchema> { + method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas(); + method @IntRange(from=0) public int getVersion(); + } + + public static final class GetSchemaResponse.Builder { + ctor public GetSchemaResponse.Builder(); + method @NonNull public android.app.appsearch.GetSchemaResponse.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema); + method @NonNull public android.app.appsearch.GetSchemaResponse build(); + method @NonNull public android.app.appsearch.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int); + } + public class GlobalSearchSession implements java.io.Closeable { method public void close(); + method public void reportSystemUsage(@NonNull android.app.appsearch.ReportSystemUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); } public abstract class Migrator { ctor public Migrator(); - ctor public Migrator(int); method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onDowngrade(int, int, @NonNull android.app.appsearch.GenericDocument); method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onUpgrade(int, int, @NonNull android.app.appsearch.GenericDocument); + method public abstract boolean shouldMigrate(int, int); } public class PackageIdentifier { @@ -246,6 +273,21 @@ package android.app.appsearch { method @Deprecated @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String); } + public final class ReportSystemUsageRequest { + method @NonNull public String getDatabaseName(); + method @NonNull public String getNamespace(); + method @NonNull public String getPackageName(); + method @NonNull public String getUri(); + method public long getUsageTimeMillis(); + } + + public static final class ReportSystemUsageRequest.Builder { + ctor public ReportSystemUsageRequest.Builder(@NonNull String, @NonNull String, @NonNull String); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest build(); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUri(@NonNull String); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUsageTimeMillis(long); + } + public final class ReportUsageRequest { method @NonNull public String getNamespace(); method @NonNull public String getUri(); @@ -267,6 +309,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.GenericDocument getGenericDocument(); method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches(); method @NonNull public String getPackageName(); + method public double getRankingSignal(); } public static final class SearchResult.Builder { @@ -274,6 +317,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo); method @NonNull public android.app.appsearch.SearchResult build(); method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public android.app.appsearch.SearchResult.Builder setRankingSignal(double); } public static final class SearchResult.MatchInfo { @@ -315,9 +359,13 @@ package android.app.appsearch { method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections(); method public int getRankingStrategy(); method public int getResultCountPerPage(); + method public int getResultGroupingLimit(); + method public int getResultGroupingTypeFlags(); method public int getSnippetCount(); method public int getSnippetCountPerProperty(); method public int getTermMatch(); + field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2 + field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1 field public static final int ORDER_ASCENDING = 1; // 0x1 field public static final int ORDER_DESCENDING = 0; // 0x0 field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; @@ -325,6 +373,8 @@ package android.app.appsearch { field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1 field public static final int RANKING_STRATEGY_NONE = 0; // 0x0 field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3 + field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6 + field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7 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 @@ -345,6 +395,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int); method @NonNull public android.app.appsearch.SearchSpec.Builder setRankingStrategy(int); method @NonNull public android.app.appsearch.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_NUM_PER_PAGE) int); + method @NonNull public android.app.appsearch.SearchSpec.Builder setResultGrouping(int, int); method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_COUNT) int); method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_PER_PROPERTY_COUNT) int); method @NonNull public android.app.appsearch.SearchSpec.Builder setTermMatch(int); @@ -356,6 +407,7 @@ package android.app.appsearch { method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem(); method @Deprecated @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages(); + method @IntRange(from=1) public int getVersion(); method public boolean isForceOverride(); } @@ -366,9 +418,11 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaRequest build(); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.Migrator); + method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrators(@NonNull java.util.Map<java.lang.String,android.app.appsearch.Migrator>); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(@NonNull String, boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier); method @Deprecated @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean); + method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int); } public class SetSchemaResponse { @@ -407,6 +461,20 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setUri(@NonNull String); } + public class StorageInfo { + method public int getAliveDocumentsCount(); + method public int getAliveNamespacesCount(); + method public long getSizeBytes(); + } + + public static final class StorageInfo.Builder { + ctor public StorageInfo.Builder(); + method @NonNull public android.app.appsearch.StorageInfo build(); + method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveDocumentsCount(int); + method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveNamespacesCount(int); + method @NonNull public android.app.appsearch.StorageInfo.Builder setSizeBytes(long); + } + } package android.app.appsearch.exceptions { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java index 31ab259127c3..35cea3e2a67b 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -30,10 +30,10 @@ import java.util.Map; /** * Provides results for AppSearch batch operations which encompass multiple documents. * - * <p>Individual results of a batch operation are separated into two maps: one for successes and one - * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of - * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link - * AppSearchResult} objects. + * <p>Individual results of a batch operation are separated into two maps: one for successes and + * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to + * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to + * {@link AppSearchResult} objects. * * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for * both successes and failures. @@ -97,8 +97,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed - * individual results. + * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all + * failed individual results. * * <p>The values of the {@link Map} will not be {@code null}. */ @@ -120,7 +120,6 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl /** * Asserts that this {@link AppSearchBatchResult} has no failures. - * * @hide */ public void checkSuccess() { @@ -162,8 +161,6 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * Builder for {@link AppSearchBatchResult} objects. * * <p>Once {@link #build} is called, the instance can no longer be used. - * - * @hide */ public static final class Builder<KeyType, ValueType> { private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>(); @@ -178,6 +175,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses @NonNull public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { @@ -193,6 +191,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures @NonNull public Builder<KeyType, ValueType> setFailure( @NonNull KeyType key, @@ -210,6 +209,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getAll @NonNull public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java index 440f63341151..b66837d1f679 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,20 +39,19 @@ import java.util.Objects; public final class AppSearchResult<ValueType> implements Parcelable { /** * Result codes from {@link AppSearchSession} methods. - * * @hide */ - @IntDef( - value = { - RESULT_OK, - RESULT_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_IO_ERROR, - RESULT_OUT_OF_SPACE, - RESULT_NOT_FOUND, - RESULT_INVALID_SCHEMA, - }) + @IntDef(value = { + RESULT_OK, + RESULT_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_IO_ERROR, + RESULT_OUT_OF_SPACE, + RESULT_NOT_FOUND, + RESULT_INVALID_SCHEMA, + RESULT_SECURITY_ERROR, + }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} @@ -90,6 +91,9 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** The caller supplied a schema which is invalid or incompatible with the previous schema. */ public static final int RESULT_INVALID_SCHEMA = 7; + /** The caller requested an operation it does not have privileges for. */ + public static final int RESULT_SECURITY_ERROR = 8; + private final @ResultCode int mResultCode; @Nullable private final ValueType mResultValue; @Nullable private final String mErrorMessage; @@ -148,8 +152,8 @@ public final class AppSearchResult<ValueType> implements Parcelable { * * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the - * documentation of the particular {@link AppSearchSession} call producing this {@link - * AppSearchResult} for what is returned by {@link #getErrorMessage}. + * documentation of the particular {@link AppSearchSession} call producing this + * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}. */ @Nullable public String getErrorMessage() { @@ -208,8 +212,6 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new successful {@link AppSearchResult}. - * - * @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult( @@ -219,8 +221,6 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new failed {@link AppSearchResult}. - * - * @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> newFailedResult( @@ -228,6 +228,20 @@ public final class AppSearchResult<ValueType> implements Parcelable { return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage); } + /** + * Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type. + * + * @hide + */ + @NonNull + public static <ValueType> AppSearchResult<ValueType> newFailedResult( + @NonNull AppSearchResult<?> otherFailedResult) { + Preconditions.checkState(!otherFailedResult.isSuccess(), + "Cannot convert a success result to a failed result"); + return AppSearchResult.newFailedResult( + otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage()); + } + /** @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult( diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 9ea73a9773bc..bf733ed77442 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -45,11 +45,13 @@ import java.util.function.Consumer; */ public final class AppSearchSession implements Closeable { private static final String TAG = "AppSearchSession"; + private final String mPackageName; private final String mDatabaseName; @UserIdInt private final int mUserId; private final IAppSearchManager mService; + private boolean mIsMutated = false; private boolean mIsClosed = false; @@ -102,6 +104,18 @@ public final class AppSearchSession implements Closeable { } /** + * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. + * @deprecated This method exists only for dogfooder transition and must be removed. + */ + @Deprecated + public void setSchema( + @NonNull SetSchemaRequest request, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { + setSchema(request, callbackExecutor, callbackExecutor, callback); + } + + /** * Sets the schema that represents the organizational structure of data within the AppSearch * database. * @@ -111,7 +125,9 @@ public final class AppSearchSession implements Closeable { * no-op call. * * @param request the schema to set or update the AppSearch database to. - * @param executor Executor on which to invoke the callback. + * @param workExecutor Executor on which to schedule heavy client-side background work such as + * transforming documents. + * @param callbackExecutor 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}. */ @@ -119,10 +135,12 @@ public final class AppSearchSession implements Closeable { // exposed. public void setSchema( @NonNull SetSchemaRequest request, - @NonNull @CallbackExecutor Executor executor, + @NonNull Executor workExecutor, + @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { Objects.requireNonNull(request); - Objects.requireNonNull(executor); + Objects.requireNonNull(workExecutor); + Objects.requireNonNull(callbackExecutor); Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size()); @@ -148,12 +166,15 @@ public final class AppSearchSession implements Closeable { schemasPackageAccessibleBundles, request.isForceOverride(), mUserId, + request.getVersion(), new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { - executor.execute(() -> { + callbackExecutor.execute(() -> { if (result.isSuccess()) { callback.accept( // TODO(b/177266929) implement Migration in platform. + // TODO(b/183177268): once migration is implemented, run + // it on workExecutor. AppSearchResult.newSuccessfulResult( new SetSchemaResponse.Builder().build())); } else { @@ -176,7 +197,7 @@ public final class AppSearchSession implements Closeable { */ public void getSchema( @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<AppSearchResult<Set<AppSearchSchema>>> callback) { + @NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); @@ -189,14 +210,46 @@ public final class AppSearchSession implements Closeable { public void onResult(AppSearchResult result) { executor.execute(() -> { if (result.isSuccess()) { - List<Bundle> schemaBundles = - (List<Bundle>) result.getResultValue(); - Set<AppSearchSchema> schemas = new ArraySet<>( - schemaBundles.size()); - for (int i = 0; i < schemaBundles.size(); i++) { - schemas.add(new AppSearchSchema(schemaBundles.get(i))); - } - callback.accept(AppSearchResult.newSuccessfulResult(schemas)); + Bundle responseBundle = (Bundle) result.getResultValue(); + GetSchemaResponse response = + new GetSchemaResponse(responseBundle); + callback.accept(AppSearchResult.newSuccessfulResult(response)); + } else { + callback.accept(result); + } + }); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive the namespaces. + */ + public void getNamespaces( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<Set<String>>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); + try { + mService.getNamespaces( + mPackageName, + mDatabaseName, + mUserId, + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> { + if (result.isSuccess()) { + Set<String> namespaces = + new ArraySet<>((List<String>) result.getResultValue()); + callback.accept( + AppSearchResult.newSuccessfulResult(namespaces)); } else { callback.accept(result); } @@ -297,8 +350,7 @@ public final class AppSearchSession implements Closeable { // Translate successful results for (Map.Entry<String, Bundle> bundleEntry : - (Set<Map.Entry<String, Bundle>>) - result.getSuccesses().entrySet()) { + ((Map<String, Bundle>) result.getSuccesses()).entrySet()) { GenericDocument document; try { document = new GenericDocument(bundleEntry.getValue()); @@ -317,8 +369,8 @@ public final class AppSearchSession implements Closeable { // Translate failed results for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry : - (Set<Map.Entry<String, AppSearchResult<Bundle>>>) - result.getFailures().entrySet()) { + ((Map<String, AppSearchResult<Bundle>>) + result.getFailures()).entrySet()) { documentResultBuilder.setFailure( bundleEntry.getKey(), bundleEntry.getValue().getResultCode(), @@ -437,6 +489,7 @@ public final class AppSearchSession implements Closeable { request.getNamespace(), request.getUri(), request.getUsageTimeMillis(), + /*systemUsage=*/ false, mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { @@ -541,6 +594,25 @@ public final class AppSearchSession implements Closeable { } /** + * Gets the storage info for this {@link AppSearchSession} database. + * + * <p>This may take time proportional to the number of documents and may be inefficient to + * call repeatedly. + * + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive the storage info. + */ + public void getStorageInfo( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<StorageInfo>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); + // TODO(b/182909475): Implement getStorageInfo + throw new UnsupportedOperationException(); + } + + /** * Closes the {@link AppSearchSession} to persist all schema and document updates, additions, * and deletes to disk. */ diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index fb63e16aa6a8..b30ed50b8240 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.os.RemoteException; +import android.util.Log; import com.android.internal.util.Preconditions; @@ -35,14 +36,15 @@ import java.util.function.Consumer; * <p>Apps can retrieve indexed documents through the {@link #search} API. */ public class GlobalSearchSession implements Closeable { + private static final String TAG = "AppSearchGlobalSearchSe"; - private final IAppSearchManager mService; - + private final String mPackageName; @UserIdInt private final int mUserId; - private boolean mIsClosed = false; + private final IAppSearchManager mService; - private final String mPackageName; + private boolean mIsMutated = false; + private boolean mIsClosed = false; /** * Creates a search session for the client, defined by the {@code userId} and @@ -117,9 +119,66 @@ public class GlobalSearchSession implements Closeable { searchSpec, mUserId); } - /** Closes the {@link GlobalSearchSession}. */ + /** + * Reports that a particular document has been used from a system surface. + * + * <p>See {@link AppSearchSession#reportUsage} for a general description of document usage, as + * well as an API that can be used by the app itself. + * + * <p>Usage reported via this method is accounted separately from usage reported via + * {@link AppSearchSession#reportUsage} and may be accessed using the constants + * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and + * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}. + * + * @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 an {@link AppSearchResult} whose value is {@code null}. The + * callback will be invoked with an {@link AppSearchResult} of + * {@link AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an + * app which is not part of the system. + */ + public void reportSystemUsage( + @NonNull ReportSystemUsageRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<Void>> callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed"); + try { + mService.reportUsage( + request.getPackageName(), + request.getDatabaseName(), + request.getNamespace(), + request.getUri(), + request.getUsageTimeMillis(), + /*systemUsage=*/ true, + mUserId, + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> callback.accept(result)); + } + }); + mIsMutated = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Closes the {@link GlobalSearchSession}. Persists all mutations, including usage reports, to + * disk. + */ @Override public void close() { - mIsClosed = true; + if (mIsMutated && !mIsClosed) { + try { + mService.persistToDisk(mUserId); + mIsClosed = true; + } catch (RemoteException e) { + Log.e(TAG, "Unable to close the GlobalSearchSession", e); + } + } } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index ba27762c7dfb..d436488f34f2 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -51,6 +51,7 @@ interface IAppSearchManager { in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, + in int schemaVersion, in IAppSearchResultCallback callback); /** @@ -60,8 +61,7 @@ interface IAppSearchManager { * @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. + * {@link AppSearchResult}<{@link Bundle}> where the bundle is a GetSchemaResponse. */ void getSchema( in String packageName, @@ -70,6 +70,21 @@ interface IAppSearchManager { in IAppSearchResultCallback callback); /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @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 String}>>. + */ + void getNamespaces( + 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. @@ -190,6 +205,7 @@ interface IAppSearchManager { * @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 systemUsage Whether the usage was reported by a system app against another app's doc. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an * {@link AppSearchResult}<{@link Void}>. @@ -200,6 +216,7 @@ interface IAppSearchManager { in String namespace, in String uri, in long usageTimeMillis, + in boolean systemUsage, in int userId, in IAppSearchResultCallback callback); 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 55f0c80b16d0..79b7b7552196 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java @@ -46,7 +46,6 @@ import java.util.Set; */ public final class AppSearchSchema { private static final String SCHEMA_TYPE_FIELD = "schemaType"; - private static final String VERSION_FIELD = "version"; private static final String PROPERTIES_FIELD = "properties"; private final Bundle mBundle; @@ -78,9 +77,10 @@ public final class AppSearchSchema { return mBundle.getString(SCHEMA_TYPE_FIELD, ""); } - /** Returns the version of this {@link AppSearchSchema}. */ + /** @deprecated Use {@link GetSchemaResponse#getVersion()} instead. */ + @Deprecated public @IntRange(from = 0) int getVersion() { - return mBundle.getInt(VERSION_FIELD); + return 0; } /** @@ -115,15 +115,12 @@ public final class AppSearchSchema { if (!getSchemaType().equals(otherSchema.getSchemaType())) { return false; } - if (getVersion() != otherSchema.getVersion()) { - return false; - } return getProperties().equals(otherSchema.getProperties()); } @Override public int hashCode() { - return Objects.hash(getSchemaType(), getVersion(), getProperties()); + return Objects.hash(getSchemaType(), getProperties()); } /** Builder for {@link AppSearchSchema objects}. */ @@ -131,7 +128,6 @@ public final class AppSearchSchema { private final String mSchemaType; private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>(); private final Set<String> mPropertyNames = new ArraySet<>(); - private int mVersion; private boolean mBuilt = false; /** Creates a new {@link AppSearchSchema.Builder}. */ @@ -154,38 +150,13 @@ public final class AppSearchSchema { } /** - * Sets the version number of the {@link AppSearchSchema}. - * - * <p>The {@link AppSearchSession} database can only ever hold documents for one version of - * a {@link AppSearchSchema} type at a time. - * - * <p>Setting a version number that is different from the version number of the schema - * currently stored in AppSearch will result in AppSearch calling the {@link Migrator} - * provided to {@link AppSearchSession#setSchema} to migrate the documents already in - * AppSearch from the previous version to the one set in this request. The version number - * can be updated without any other changes to the schema. - * - * <p>The version number can stay the same, increase, or decrease relative to the current - * version number of the {@link AppSearchSchema} type that is already stored in the {@link - * AppSearchSession} database. - * - * <p>The version number will be updated if the {@link SetSchemaRequest} contains - * backwards-compatible changes or {@link SetSchemaRequest.Builder#setForceOverride} method - * is set to {@code true}. - * - * @param version A non-negative int number represents the version of this {@link - * AppSearchSchema}, default version is 0. - * @throws IllegalStateException if the version is negative or the builder has already been - * used. - * @see AppSearchSession#setSchema - * @see Migrator - * @see SetSchemaRequest.Builder#setMigrator + * @deprecated TODO(b/181887768): This method is a no-op and only exists for dogfooder + * transition. */ + @Deprecated @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; } @@ -199,7 +170,6 @@ public final class AppSearchSchema { Preconditions.checkState(!mBuilt, "Builder has already been used"); Bundle bundle = new Bundle(); bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType); - bundle.putInt(AppSearchSchema.VERSION_FIELD, mVersion); bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles); mBuilt = true; return new AppSearchSchema(bundle); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java new file mode 100644 index 000000000000..3e693670f7bc --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java @@ -0,0 +1,116 @@ +/* + * 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.IntRange; +import android.annotation.NonNull; +import android.os.Bundle; +import android.util.ArraySet; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** The response class of {@link AppSearchSession#getSchema} */ +// TODO(b/181887768) extends only for dogfooder transition. */ +public class GetSchemaResponse extends HashSet<AppSearchSchema> { + private static final String VERSION_FIELD = "version"; + private static final String SCHEMAS_FIELD = "schemas"; + + private final Bundle mBundle; + + // TODO(b/181887768) Remove this method once this class no longer extends HashSet. */ + private static Set<AppSearchSchema> getSchemasFromBundle(Bundle bundle) { + ArrayList<Bundle> schemaBundles = bundle.getParcelableArrayList(SCHEMAS_FIELD); + Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size()); + for (int i = 0; i < schemaBundles.size(); i++) { + schemas.add(new AppSearchSchema(schemaBundles.get(i))); + } + return schemas; + } + + GetSchemaResponse(@NonNull Bundle bundle) { + super(getSchemasFromBundle(Preconditions.checkNotNull(bundle))); + mBundle = bundle; + } + + /** + * Returns the {@link Bundle} populated by this builder. + * + * @hide + */ + @NonNull + public Bundle getBundle() { + return mBundle; + } + + /** + * Returns the overall database schema version. + * + * <p>If the database is empty, 0 will be returned. + */ + @IntRange(from = 0) + public int getVersion() { + return mBundle.getInt(VERSION_FIELD); + } + + /** + * Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}. + */ + @NonNull + public Set<AppSearchSchema> getSchemas() { + return this; + } + + /** Builder for {@link GetSchemaResponse} objects. */ + public static final class Builder { + private int mVersion; + private boolean mBuilt = false; + private final ArrayList<Bundle> mSchemaBundles = new ArrayList<>(); + + /** + * Sets the database overall schema version. + * + * <p>Default version is 0 + */ + @NonNull + public Builder setVersion(@IntRange(from = 0) int version) { + mVersion = version; + return this; + } + + /** Adds one {@link AppSearchSchema} to the schema list. */ + @NonNull + public Builder addSchema(@NonNull AppSearchSchema schema) { + mSchemaBundles.add(schema.getBundle()); + return this; + } + + /** Builds a {@link GetSchemaResponse} object. */ + @NonNull + public GetSchemaResponse build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Bundle bundle = new Bundle(); + bundle.putInt(VERSION_FIELD, mVersion); + bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles); + mBuilt = true; + return new GetSchemaResponse(bundle); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java index 5ae9a4186a52..511b42ac8f12 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java @@ -19,8 +19,6 @@ package android.app.appsearch; import android.annotation.NonNull; import android.annotation.WorkerThread; -import com.android.internal.util.Preconditions; - /** * A migrator class to translate {@link GenericDocument} from different version of {@link * AppSearchSchema} @@ -37,37 +35,14 @@ import com.android.internal.util.Preconditions; * documents won't have any observable changes. */ public abstract class Migrator { - private final int mStartVersion; - - /** - * Creates a {@link Migrator} will trigger migration for any version less than the final version - * in the new schema. - */ - public Migrator() { - this(/*startVersion=*/ 0); - } - /** - * Creates a {@link Migrator} with a non-negative start version. - * - * <p>Providing 0 will trigger migration for any version less than the final version in the new - * schema. + * Returns {@code true} if this migrator's source type needs to be migrated to update from + * currentVersion to finalVersion. * - * @param startVersion The migration will be only triggered for those versions greater or equal - * to the given startVersion. + * <p>Migration won't be triggered if currentVersion is equal to finalVersion even if {@link + * #shouldMigrate} return true; */ - public Migrator(int startVersion) { - Preconditions.checkArgumentNonnegative(startVersion); - mStartVersion = startVersion; - } - - /** - * @return {@code True} if the current version need to be migrated. - * @hide - */ - public boolean shouldMigrateToFinalVersion(int currentVersion, int finalVersion) { - return currentVersion >= mStartVersion && currentVersion != finalVersion; - } + public abstract boolean shouldMigrate(int currentVersion, int finalVersion); /** * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}. @@ -75,17 +50,22 @@ public abstract class Migrator { * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a higher * version number than the current {@link AppSearchSchema} saved in AppSearch. * - * <p>This method will be invoked on the background worker thread. + * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link + * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use + * the same URI. + * + * <p>This method will be invoked on the background worker thread provided via {@link + * AppSearchSession#setSchema}. * * @param currentVersion The current version of the document's schema. - * @param targetVersion The final version that documents need to be migrated to. + * @param finalVersion The final version that documents need to be migrated to. * @param document The {@link GenericDocument} need to be translated to new version. * @return A {@link GenericDocument} in new version. */ @WorkerThread @NonNull public abstract GenericDocument onUpgrade( - int currentVersion, int targetVersion, @NonNull GenericDocument document); + int currentVersion, int finalVersion, @NonNull GenericDocument document); /** * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}. @@ -93,15 +73,19 @@ public abstract class Migrator { * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a lower * version number than the current {@link AppSearchSchema} saved in AppSearch. * + * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link + * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use + * the same URI. + * * <p>This method will be invoked on the background worker thread. * * @param currentVersion The current version of the document's schema. - * @param targetVersion The final version that documents need to be migrated to. + * @param finalVersion The final version that documents need to be migrated to. * @param document The {@link GenericDocument} need to be translated to new version. * @return A {@link GenericDocument} in new version. */ @WorkerThread @NonNull public abstract GenericDocument onDowngrade( - int currentVersion, int targetVersion, @NonNull GenericDocument document); + int currentVersion, int finalVersion, @NonNull GenericDocument document); } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java new file mode 100644 index 000000000000..2e152f89465f --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java @@ -0,0 +1,154 @@ +/* + * 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 owned by another app from a system UI surface. + * + * <p>Usage reported in this way is measured separately from usage reported via {@link + * AppSearchSession#reportUsage}. + * + * <p>See {@link GlobalSearchSession#reportSystemUsage} for a detailed description of usage + * reporting. + */ +public final class ReportSystemUsageRequest { + private final String mPackageName; + private final String mDatabase; + private final String mNamespace; + private final String mUri; + private final long mUsageTimeMillis; + + ReportSystemUsageRequest( + @NonNull String packageName, + @NonNull String database, + @NonNull String namespace, + @NonNull String uri, + long usageTimeMillis) { + mPackageName = Preconditions.checkNotNull(packageName); + mDatabase = Preconditions.checkNotNull(database); + mNamespace = Preconditions.checkNotNull(namespace); + mUri = Preconditions.checkNotNull(uri); + mUsageTimeMillis = usageTimeMillis; + } + + /** Returns the package name of the app which owns the document that was used. */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** Returns the database in which the document that was used resides. */ + @NonNull + public String getDatabaseName() { + return mDatabase; + } + + /** 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 ReportSystemUsageRequest} objects. */ + public static final class Builder { + private final String mPackageName; + private final String mDatabase; + private final String mNamespace; + private String mUri; + private Long mUsageTimeMillis; + private boolean mBuilt = false; + + /** Creates a {@link ReportSystemUsageRequest.Builder} instance. */ + public Builder( + @NonNull String packageName, @NonNull String database, @NonNull String namespace) { + mPackageName = Preconditions.checkNotNull(packageName); + mDatabase = Preconditions.checkNotNull(database); + mNamespace = Preconditions.checkNotNull(namespace); + } + + /** + * Sets the URI of the document being used. + * + * <p>This field is required. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest.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 + * ReportSystemUsageRequest} is constructed. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest.Builder setUsageTimeMillis(long usageTimeMillis) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mUsageTimeMillis = usageTimeMillis; + return this; + } + + /** + * Builds a new {@link ReportSystemUsageRequest}. + * + * @throws NullPointerException if {@link #setUri} has never been called + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest 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 ReportSystemUsageRequest( + mPackageName, mDatabase, 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 cb20849dd36f..e66056f0380b 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java @@ -47,6 +47,7 @@ public final class SearchResult { static final String MATCHES_FIELD = "matches"; static final String PACKAGE_NAME_FIELD = "packageName"; static final String DATABASE_NAME_FIELD = "databaseName"; + static final String RANKING_SIGNAL_FIELD = "rankingSignal"; @NonNull private final Bundle mBundle; @@ -131,6 +132,35 @@ public final class SearchResult { return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD)); } + /** + * Returns the ranking signal of the {@link GenericDocument}, according to the ranking strategy + * set in {@link SearchSpec.Builder#setRankingStrategy(int)}. + * + * <p>The meaning of the ranking signal and its value is determined by the selected ranking + * strategy: + * + * <ul> + * <li>{@link SearchSpec#RANKING_STRATEGY_NONE} - this value will be 0 + * <li>{@link SearchSpec#RANKING_STRATEGY_DOCUMENT_SCORE} - the value returned by calling + * {@link GenericDocument#getScore()} on the document returned by {@link #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling + * {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link + * #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_RELEVANCE_SCORE} - an arbitrary double value where a + * higher value means more relevant + * <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} - the number of times usage has been + * reported for the document returned by {@link #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} - the timestamp of the + * most recent usage that has been reported for the document returned by {@link + * #getDocument()} + * </ul> + * + * @return Ranking signal of the document + */ + public double getRankingSignal() { + return mBundle.getDouble(RANKING_SIGNAL_FIELD); + } + /** Builder for {@link SearchResult} objects. */ public static final class Builder { private final Bundle mBundle = new Bundle(); @@ -173,6 +203,14 @@ public final class SearchResult { return this; } + /** Sets the ranking signal of the matched document in this SearchResult. */ + @NonNull + public Builder setRankingSignal(double rankingSignal) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putDouble(RANKING_SIGNAL_FIELD, rankingSignal); + return this; + } + /** * Constructs a new {@link SearchResult}. * 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 7888c8d78cd8..19d94305b3da 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java @@ -19,6 +19,7 @@ package android.app.appsearch; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.appsearch.exceptions.IllegalSearchSpecException; import android.os.Bundle; import android.util.ArrayMap; @@ -58,6 +59,8 @@ public final class SearchSpec { static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty"; static final String MAX_SNIPPET_FIELD = "maxSnippet"; static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks"; + static final String RESULT_GROUPING_TYPE_FLAGS = "resultGroupingTypeFlags"; + static final String RESULT_GROUPING_LIMIT = "resultGroupingLimit"; /** @hide */ public static final int DEFAULT_NUM_PER_PAGE = 10; @@ -107,7 +110,9 @@ public final class SearchSpec { RANKING_STRATEGY_CREATION_TIMESTAMP, RANKING_STRATEGY_RELEVANCE_SCORE, RANKING_STRATEGY_USAGE_COUNT, - RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP + RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP, + RANKING_STRATEGY_SYSTEM_USAGE_COUNT, + RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP, }) @Retention(RetentionPolicy.SOURCE) public @interface RankingStrategy {} @@ -120,10 +125,14 @@ public final class SearchSpec { public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; /** Ranked by document relevance score. */ public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; - /** Ranked by number of usages. */ + /** Ranked by number of usages, as reported by the app. */ public static final int RANKING_STRATEGY_USAGE_COUNT = 4; - /** Ranked by timestamp of last usage. */ + /** Ranked by timestamp of last usage, as reported by the app. */ public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; + /** Ranked by number of usages from a system UI surface. */ + public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; + /** Ranked by timestamp of last usage from a system UI surface. */ + public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; /** * Order for query result. @@ -141,6 +150,28 @@ public final class SearchSpec { /** Search results will be returned in an ascending order. */ public static final int ORDER_ASCENDING = 1; + /** + * Grouping type for result limits. + * + * @hide + */ + @IntDef( + flag = true, + value = {GROUPING_TYPE_PER_PACKAGE, GROUPING_TYPE_PER_NAMESPACE}) + @Retention(RetentionPolicy.SOURCE) + public @interface GroupingType {} + + /** + * Results should be grouped together by package for the purpose of enforcing a limit on the + * number of results returned per package. + */ + public static final int GROUPING_TYPE_PER_PACKAGE = 0b01; + /** + * Results should be grouped together by namespace for the purpose of enforcing a limit on the + * number of results returned per namespace. + */ + public static final int GROUPING_TYPE_PER_NAMESPACE = 0b10; + private final Bundle mBundle; /** @hide */ @@ -259,6 +290,24 @@ public final class SearchSpec { return typePropertyPathsMap; } + /** + * Get the type of grouping limit to apply, or 0 if {@link Builder#setResultGrouping} was not + * called. + */ + public @GroupingType int getResultGroupingTypeFlags() { + return mBundle.getInt(RESULT_GROUPING_TYPE_FLAGS); + } + + /** + * Get the maximum number of results to return for each group. + * + * @return the maximum number of results to return for each group or Integer.MAX_VALUE if {@link + * Builder#setResultGrouping(int, int)} was not called. + */ + public int getResultGroupingLimit() { + return mBundle.getInt(RESULT_GROUPING_LIMIT, Integer.MAX_VALUE); + } + /** Builder for {@link SearchSpec objects}. */ public static final class Builder { @@ -391,7 +440,7 @@ public final class SearchSpec { Preconditions.checkArgumentInRange( rankingStrategy, RANKING_STRATEGY_NONE, - RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP, + RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP, "Result ranking strategy"); mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy); return this; @@ -549,6 +598,34 @@ public final class SearchSpec { } /** + * Set the maximum number of results to return for each group, where groups are defined by + * grouping type. + * + * <p>Calling this method will override any previous calls. So calling + * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 7) and then calling + * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 2) will result in only the latter, a limit + * of two results per package, being applied. Or calling setResultGrouping + * (GROUPING_TYPE_PER_PACKAGE, 1) and then calling setResultGrouping + * (GROUPING_TYPE_PER_PACKAGE | GROUPING_PER_NAMESPACE, 5) will result in five results per + * package per namespace. + * + * @param groupingTypeFlags One or more combination of grouping types. + * @param limit Number of results to return per {@code groupingTypeFlags}. + * @throws IllegalArgumentException if groupingTypeFlags is zero. + */ + // Individual parameters available from getResultGroupingTypeFlags and + // getResultGroupingLimit + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) { + Preconditions.checkState( + groupingTypeFlags != 0, "Result grouping type cannot be zero."); + mBundle.putInt(RESULT_GROUPING_TYPE_FLAGS, groupingTypeFlags); + mBundle.putInt(RESULT_GROUPING_LIMIT, limit); + return this; + } + + /** * Constructs a new {@link SearchSpec} from the contents of this builder. * * <p>After calling this method, the builder must no longer be used. diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java index c1eedcd63dbf..e840ffcc69b1 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java @@ -16,6 +16,7 @@ package android.app.appsearch; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.util.ArrayMap; @@ -84,18 +85,21 @@ public final class SetSchemaRequest { private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; private final Map<String, Migrator> mMigrators; private final boolean mForceOverride; + private final int mVersion; SetSchemaRequest( @NonNull Set<AppSearchSchema> schemas, @NonNull Set<String> schemasNotDisplayedBySystem, @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, @NonNull Map<String, Migrator> migrators, - boolean forceOverride) { + boolean forceOverride, + int version) { mSchemas = Preconditions.checkNotNull(schemas); mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem); mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages); mMigrators = Preconditions.checkNotNull(migrators); mForceOverride = forceOverride; + mVersion = version; } /** Returns the {@link AppSearchSchema} types that are part of this request. */ @@ -166,6 +170,12 @@ public final class SetSchemaRequest { return mForceOverride; } + /** Returns the database overall schema version. */ + @IntRange(from = 1) + public int getVersion() { + return mVersion; + } + /** * Builder for {@link SetSchemaRequest} objects. * @@ -178,6 +188,7 @@ public final class SetSchemaRequest { new ArrayMap<>(); private final Map<String, Migrator> mMigrators = new ArrayMap<>(); private boolean mForceOverride = false; + private int mVersion = 1; private boolean mBuilt = false; /** @@ -323,6 +334,19 @@ public final class SetSchemaRequest { } /** + * Sets {@link Migrator}s. + * + * @param migrators A {@link Map} of migrators that translate a document from its old + * version to a new incompatible version. + */ + @NonNull + public Builder setMigrators(@NonNull Map<String, Migrator> migrators) { + Preconditions.checkNotNull(migrators); + mMigrators.putAll(migrators); + return this; + } + + /** * Sets whether or not to override the current schema in the {@link AppSearchSession} * database. * @@ -340,6 +364,37 @@ public final class SetSchemaRequest { } /** + * Sets the version number of the overall {@link AppSearchSchema} in the database. + * + * <p>The {@link AppSearchSession} database can only ever hold documents for one version at + * a time. + * + * <p>Setting a version number that is different from the version number currently stored in + * AppSearch will result in AppSearch calling the {@link Migrator}s 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 set of schemas. + * + * <p>The version number can stay the same, increase, or decrease relative to the current + * version number that is already stored in the {@link AppSearchSession} database. + * + * @param version A positive integer representing the version of the entire set of schemas + * represents the version of the whole schema in the {@link AppSearchSession} database, + * default version is 1. + * @throws IllegalStateException if the version is negative or the builder has already been + * used. + * @see AppSearchSession#setSchema + * @see Migrator + * @see SetSchemaRequest.Builder#setMigrator + */ + @NonNull + public Builder setVersion(@IntRange(from = 1) int version) { + Preconditions.checkArgument(version >= 1, "Version must be a positive number."); + mVersion = version; + return this; + } + + /** * Builds a new {@link SetSchemaRequest} object. * * @throws IllegalArgumentException if schema types were referenced, but the corresponding @@ -372,7 +427,8 @@ public final class SetSchemaRequest { mSchemasNotDisplayedBySystem, mSchemasVisibleToPackages, mMigrators, - mForceOverride); + mForceOverride, + mVersion); } } } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java new file mode 100644 index 000000000000..dc04cf3068ce --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; +import android.os.Bundle; + +import com.android.internal.util.Preconditions; + +/** The response class of {@code AppSearchSession#getStorageInfo}. */ +public class StorageInfo { + + private static final String SIZE_BYTES_FIELD = "sizeBytes"; + private static final String ALIVE_DOCUMENTS_COUNT = "aliveDocumentsCount"; + private static final String ALIVE_NAMESPACES_COUNT = "aliveNamespacesCount"; + + private final Bundle mBundle; + + StorageInfo(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + } + + /** + * Returns the {@link Bundle} populated by this builder. + * + * @hide + */ + @NonNull + public Bundle getBundle() { + return mBundle; + } + + /** Returns the estimated size of the session's database in bytes. */ + public long getSizeBytes() { + return mBundle.getLong(SIZE_BYTES_FIELD); + } + + /** + * Returns the number of alive documents in the current session. + * + * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as + * set in {@link GenericDocument.Builder#setTtlMillis}. + */ + public int getAliveDocumentsCount() { + return mBundle.getInt(ALIVE_DOCUMENTS_COUNT); + } + + /** + * Returns the number of namespaces that have at least one alive document in the current + * session's database. + * + * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as + * set in {@link GenericDocument.Builder#setTtlMillis}. + */ + public int getAliveNamespacesCount() { + return mBundle.getInt(ALIVE_NAMESPACES_COUNT); + } + + /** Builder for {@link StorageInfo} objects. */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Sets the size in bytes. */ + @NonNull + public StorageInfo.Builder setSizeBytes(long sizeBytes) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putLong(SIZE_BYTES_FIELD, sizeBytes); + return this; + } + + /** Sets the number of alive documents. */ + @NonNull + public StorageInfo.Builder setAliveDocumentsCount(int numAliveDocuments) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putInt(ALIVE_DOCUMENTS_COUNT, numAliveDocuments); + return this; + } + + /** Sets the number of alive namespaces. */ + @NonNull + public StorageInfo.Builder setAliveNamespacesCount(int numAliveNamespaces) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putInt(ALIVE_NAMESPACES_COUNT, numAliveNamespaces); + return this; + } + + /** Builds a {@link StorageInfo} object. */ + @NonNull + public StorageInfo build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBuilt = true; + return new StorageInfo(mBundle); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java index fae8ad48e61c..32d7e043e954 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java @@ -20,12 +20,11 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.Migrator; +import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Log; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -36,83 +35,70 @@ import java.util.Set; * @hide */ public final class SchemaMigrationUtil { - private static final String TAG = "AppSearchMigrateUtil"; - private SchemaMigrationUtil() {} - /** - * Finds out which incompatible schema type won't be migrated by comparing its current and final - * version number. - */ + /** Returns all active {@link Migrator}s that need to be triggered in this migration. */ @NonNull - public static Set<String> getUnmigratedIncompatibleTypes( - @NonNull Set<String> incompatibleSchemaTypes, + public static Map<String, Migrator> getActiveMigrators( + @NonNull Set<AppSearchSchema> existingSchemas, @NonNull Map<String, Migrator> migrators, - @NonNull Map<String, Integer> currentVersionMap, - @NonNull Map<String, Integer> finalVersionMap) - throws AppSearchException { - Set<String> unmigratedSchemaTypes = new ArraySet<>(); - for (String unmigratedSchemaType : incompatibleSchemaTypes) { - Integer currentVersion = currentVersionMap.get(unmigratedSchemaType); - Integer finalVersion = finalVersionMap.get(unmigratedSchemaType); - if (currentVersion == null) { - // impossible, we have done something wrong. - throw new AppSearchException( - AppSearchResult.RESULT_UNKNOWN_ERROR, - "Cannot find the current version number for schema type: " - + unmigratedSchemaType); - } - if (finalVersion == null) { - // The schema doesn't exist in the SetSchemaRequest. - unmigratedSchemaTypes.add(unmigratedSchemaType); - continue; - } - // we don't have migrator or won't trigger migration for this schema type. - Migrator migrator = migrators.get(unmigratedSchemaType); - if (migrator == null - || !migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion)) { - unmigratedSchemaTypes.add(unmigratedSchemaType); + int currentVersion, + int finalVersion) { + if (currentVersion == finalVersion) { + return Collections.emptyMap(); + } + Set<String> existingTypes = new ArraySet<>(existingSchemas.size()); + for (AppSearchSchema schema : existingSchemas) { + existingTypes.add(schema.getSchemaType()); + } + + Map<String, Migrator> activeMigrators = new ArrayMap<>(); + for (Map.Entry<String, Migrator> entry : migrators.entrySet()) { + // The device contains the source type, and we should trigger migration for the type. + String schemaType = entry.getKey(); + Migrator migrator = entry.getValue(); + if (existingTypes.contains(schemaType) + && migrator.shouldMigrate(currentVersion, finalVersion)) { + activeMigrators.put(schemaType, migrator); } } - return Collections.unmodifiableSet(unmigratedSchemaTypes); + return activeMigrators; } /** - * Triggers upgrade or downgrade migration for the given schema type if its version stored in - * AppSearch is different with the version in the request. - * - * @return {@code True} if we trigger the migration for the given type. + * Checks the setSchema() call won't delete any types or has incompatible types after all {@link + * Migrator} has been triggered.. */ - public static boolean shouldTriggerMigration( - @NonNull String schemaType, - @NonNull Migrator migrator, - @NonNull Map<String, Integer> currentVersionMap, - @NonNull Map<String, Integer> finalVersionMap) + public static void checkDeletedAndIncompatibleAfterMigration( + @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators) throws AppSearchException { - Integer currentVersion = currentVersionMap.get(schemaType); - Integer finalVersion = finalVersionMap.get(schemaType); - if (currentVersion == null) { - Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch."); - return false; - } - if (finalVersion == null) { - throw new AppSearchException( - AppSearchResult.RESULT_INVALID_ARGUMENT, - "Receive a migrator for schema type : " - + schemaType - + ", but the schema doesn't exist in the request."); - } - return migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion); + Set<String> unmigratedIncompatibleTypes = + new ArraySet<>(setSchemaResponse.getIncompatibleTypes()); + unmigratedIncompatibleTypes.removeAll(activeMigrators); + + Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes()); + unmigratedDeletedTypes.removeAll(activeMigrators); + + // check if there are any unmigrated incompatible types or deleted types. If there + // are, we will getActiveMigratorsthrow an exception. That's the only case we + // swallowed in the AppSearchImpl#setSchema(). + // Since the force override is false, the schema will not have been set if there are + // any incompatible or deleted types. + checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes); } - /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */ - @NonNull - public static Map<String, Integer> buildVersionMap( - @NonNull Collection<AppSearchSchema> schemas) { - Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size()); - for (AppSearchSchema currentSchema : schemas) { - currentVersionMap.put(currentSchema.getSchemaType(), currentSchema.getVersion()); + /** Checks the setSchema() call won't delete any types or has incompatible types. */ + public static void checkDeletedAndIncompatible( + @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes) + throws AppSearchException { + if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) { + String newMessage = + "Schema is incompatible." + + "\n Deleted types: " + + deletedTypes + + "\n Incompatible types: " + + incompatibleTypes; + throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } - return currentVersionMap; } } 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 28069743d957..6e3fb82ba213 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -24,6 +24,7 @@ import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.IAppSearchBatchResultCallback; import android.app.appsearch.IAppSearchManager; import android.app.appsearch.IAppSearchResultCallback; @@ -96,6 +97,7 @@ public class AppSearchManagerService extends SystemService { @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, @UserIdInt int userId, + int schemaVersion, @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); @@ -129,7 +131,8 @@ public class AppSearchManagerService extends SystemService { schemas, schemasNotDisplayedBySystem, schemasPackageAccessible, - forceOverride); + forceOverride, + schemaVersion); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { @@ -156,13 +159,35 @@ public class AppSearchManagerService extends SystemService { verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName); - List<Bundle> schemaBundles = new ArrayList<>(schemas.size()); - for (int i = 0; i < schemas.size(); i++) { - schemaBundles.add(schemas.get(i).getBundle()); - } + GetSchemaResponse response = impl.getSchema(packageName, databaseName); invokeCallbackOnResult( - callback, AppSearchResult.newSuccessfulResult(schemaBundles)); + callback, AppSearchResult.newSuccessfulResult(response.getBundle())); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override + public void getNamespaces( + @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 { + verifyUserUnlocked(callingUserId); + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(callingUserId); + List<String> namespaces = impl.getNamespaces(packageName, databaseName); + invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(namespaces)); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -380,6 +405,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String namespace, @NonNull String uri, long usageTimeMillis, + boolean systemUsage, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Objects.requireNonNull(databaseName); @@ -391,9 +417,16 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyUserUnlocked(callingUserId); + + if (systemUsage) { + // TODO(b/183031844): Validate that the call comes from the system + } + AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis); + impl.reportUsage( + packageName, databaseName, namespace, uri, + usageTimeMillis, systemUsage); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index ad94a0a57d27..78474295078a 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -24,6 +24,7 @@ import android.annotation.UserIdInt; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; @@ -73,6 +74,9 @@ public class VisibilityStore { /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */ private static final String VISIBILITY_TYPE = "VisibilityType"; + /** Version for the visibility schema */ + private static final int SCHEMA_VERSION = 0; + /** * Property that holds the list of platform-hidden schemas, as part of the visibility settings. */ @@ -218,11 +222,10 @@ public class VisibilityStore { * @throws AppSearchException AppSearchException on AppSearchImpl error. */ public void initialize() throws AppSearchException { - List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME); + GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME); boolean hasVisibilityType = false; boolean hasPackageAccessibleType = false; - for (int i = 0; i < schemas.size(); i++) { - AppSearchSchema schema = schemas.get(i); + for (AppSearchSchema schema : getSchemaResponse.getSchemas()) { if (schema.getSchemaType().equals(VISIBILITY_TYPE)) { hasVisibilityType = true; } else if (schema.getSchemaType().equals(PACKAGE_ACCESSIBLE_TYPE)) { @@ -242,7 +245,8 @@ public class VisibilityStore { Arrays.asList(VISIBILITY_SCHEMA, PACKAGE_ACCESSIBLE_SCHEMA), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ SCHEMA_VERSION); } // Populate visibility settings set 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 5e8760ec35c2..de9d609ea17b 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 @@ -23,10 +23,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.GetSchemaResponse; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.os.Bundle; @@ -51,6 +53,7 @@ import com.google.android.icing.IcingSearchEngine; import com.google.android.icing.proto.DeleteByQueryResultProto; import com.google.android.icing.proto.DeleteResultProto; import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.DocumentStorageInfoProto; import com.google.android.icing.proto.GetAllNamespacesResultProto; import com.google.android.icing.proto.GetOptimizeInfoResultProto; import com.google.android.icing.proto.GetResultProto; @@ -58,8 +61,10 @@ import com.google.android.icing.proto.GetResultSpecProto; import com.google.android.icing.proto.GetSchemaResultProto; import com.google.android.icing.proto.IcingSearchEngineOptions; import com.google.android.icing.proto.InitializeResultProto; +import com.google.android.icing.proto.NamespaceStorageInfoProto; import com.google.android.icing.proto.OptimizeResultProto; import com.google.android.icing.proto.PersistToDiskResultProto; +import com.google.android.icing.proto.PersistType; import com.google.android.icing.proto.PropertyConfigProto; import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.PutResultProto; @@ -73,6 +78,7 @@ import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.SetSchemaResultProto; import com.google.android.icing.proto.StatusProto; +import com.google.android.icing.proto.StorageInfoResultProto; import com.google.android.icing.proto.TypePropertyMask; import com.google.android.icing.proto.UsageReport; @@ -295,11 +301,12 @@ public final class AppSearchImpl implements Closeable { * @param schemasPackageAccessible Schema types that are visible to the specified packages. * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents * which do not comply with the new schema will be deleted. + * @param version The overall version number of the request. + * @return The response contains deleted schema types and incompatible schema types of this + * call. * @throws AppSearchException On IcingSearchEngine error. If the status code is * FAILED_PRECONDITION for the incompatible change, the exception will be converted to the * SetSchemaResponse. - * @return The response contains deleted schema types and incompatible schema types of this - * call. */ @NonNull public SetSchemaResponse setSchema( @@ -308,7 +315,8 @@ public final class AppSearchImpl implements Closeable { @NonNull List<AppSearchSchema> schemas, @NonNull List<String> schemasNotPlatformSurfaceable, @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible, - boolean forceOverride) + boolean forceOverride, + int version) throws AppSearchException { mReadWriteLock.writeLock().lock(); try { @@ -320,7 +328,7 @@ public final class AppSearchImpl implements Closeable { for (int i = 0; i < schemas.size(); i++) { AppSearchSchema schema = schemas.get(i); SchemaTypeConfigProto schemaTypeProto = - SchemaToProtoConverter.toSchemaTypeConfigProto(schema); + SchemaToProtoConverter.toSchemaTypeConfigProto(schema, version); newSchemaBuilder.addTypes(schemaTypeProto); } @@ -394,8 +402,8 @@ public final class AppSearchImpl implements Closeable { * @throws AppSearchException on IcingSearchEngine error. */ @NonNull - public List<AppSearchSchema> getSchema( - @NonNull String packageName, @NonNull String databaseName) throws AppSearchException { + public GetSchemaResponse getSchema(@NonNull String packageName, @NonNull String databaseName) + throws AppSearchException { mReadWriteLock.readLock().lock(); try { throwIfClosedLocked(); @@ -403,7 +411,12 @@ public final class AppSearchImpl implements Closeable { SchemaProto fullSchema = getSchemaProtoLocked(); String prefix = createPrefix(packageName, databaseName); - List<AppSearchSchema> result = new ArrayList<>(); + GetSchemaResponse.Builder responseBuilder = new GetSchemaResponse.Builder(); + if (!fullSchema.getTypesList().isEmpty()) { + // TODO(b/183050495) find a place to store the version for the database, rather + // than read from a schema. + responseBuilder.setVersion(fullSchema.getTypes(0).getVersion()); + } for (int i = 0; i < fullSchema.getTypesCount(); i++) { String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType()); if (!prefix.equals(typePrefix)) { @@ -431,9 +444,44 @@ public final class AppSearchImpl implements Closeable { AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder); - result.add(schema); + responseBuilder.addSchema(schema); + } + return responseBuilder.build(); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + /** + * Retrieves the list of namespaces with at least one document for this package name, database. + * + * <p>This method belongs to query group. + * + * @param packageName Package name that owns this schema + * @param databaseName The name of the database where this schema lives. + * @throws AppSearchException on IcingSearchEngine error. + */ + @NonNull + public List<String> getNamespaces(@NonNull String packageName, @NonNull String databaseName) + throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + // We can't just use mNamespaceMap here because we have no way to prune namespaces from + // mNamespaceMap when they have no more documents (e.g. after setting schema to empty or + // using deleteByQuery). + GetAllNamespacesResultProto getAllNamespacesResultProto = + mIcingSearchEngineLocked.getAllNamespaces(); + checkSuccess(getAllNamespacesResultProto.getStatus()); + String prefix = createPrefix(packageName, databaseName); + List<String> results = new ArrayList<>(); + for (int i = 0; i < getAllNamespacesResultProto.getNamespacesCount(); i++) { + String prefixedNamespace = getAllNamespacesResultProto.getNamespaces(i); + if (prefixedNamespace.startsWith(prefix)) { + results.add(prefixedNamespace.substring(prefix.length())); + } } - return result; + return results; } finally { mReadWriteLock.readLock().unlock(); } @@ -749,6 +797,18 @@ public final class AppSearchImpl implements Closeable { ResultSpecProto.Builder resultSpecBuilder = SearchSpecToProtoConverter.toResultSpecProto(searchSpec).toBuilder(); + int groupingType = searchSpec.getResultGroupingTypeFlags(); + if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0 + && (groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) { + addPerPackagePerNamespaceResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0) { + addPerPackageResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) { + addPerNamespaceResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes, allowedPrefixedSchemas); ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec); @@ -810,19 +870,24 @@ public final class AppSearchImpl implements Closeable { @NonNull String databaseName, @NonNull String namespace, @NonNull String uri, - long usageTimestampMillis) + long usageTimestampMillis, + boolean systemUsage) throws AppSearchException { mReadWriteLock.writeLock().lock(); try { throwIfClosedLocked(); String prefixedNamespace = createPrefix(packageName, databaseName) + namespace; + UsageReport.UsageType usageType = + systemUsage + ? UsageReport.UsageType.USAGE_TYPE2 + : UsageReport.UsageType.USAGE_TYPE1; UsageReport report = UsageReport.newBuilder() .setDocumentNamespace(prefixedNamespace) .setDocumentUri(uri) .setUsageTimestampMs(usageTimestampMillis) - .setUsageType(UsageReport.UsageType.USAGE_TYPE1) + .setUsageType(usageType) .build(); ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report); @@ -919,6 +984,124 @@ public final class AppSearchImpl implements Closeable { } } + /** Estimates the storage usage info for a specific package. */ + @NonNull + public StorageInfo getStorageInfoForPackage(@NonNull String packageName) + throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + + Map<String, Set<String>> packageToDatabases = getPackageToDatabases(); + Set<String> databases = packageToDatabases.get(packageName); + if (databases == null) { + // Package doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + + // Accumulate all the namespaces we're interested in. + Set<String> wantedPrefixedNamespaces = new ArraySet<>(); + for (String database : databases) { + Set<String> prefixedNamespaces = + mNamespaceMapLocked.get(createPrefix(packageName, database)); + if (prefixedNamespaces != null) { + wantedPrefixedNamespaces.addAll(prefixedNamespaces); + } + } + if (wantedPrefixedNamespaces.isEmpty()) { + return new StorageInfo.Builder().build(); + } + + return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + /** Estimates the storage usage info for a specific database in a package. */ + @NonNull + public StorageInfo getStorageInfoForDatabase( + @NonNull String packageName, @NonNull String databaseName) throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + + Map<String, Set<String>> packageToDatabases = getPackageToDatabases(); + Set<String> databases = packageToDatabases.get(packageName); + if (databases == null) { + // Package doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + if (!databases.contains(databaseName)) { + // Database doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + + Set<String> wantedPrefixedNamespaces = + mNamespaceMapLocked.get(createPrefix(packageName, databaseName)); + if (wantedPrefixedNamespaces == null || wantedPrefixedNamespaces.isEmpty()) { + return new StorageInfo.Builder().build(); + } + + return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + @GuardedBy("mReadWriteLock") + @NonNull + private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces) + throws AppSearchException { + StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo(); + checkSuccess(storageInfoResult.getStatus()); + if (!storageInfoResult.hasStorageInfo() + || !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) { + return new StorageInfo.Builder().build(); + } + long totalStorageSize = storageInfoResult.getStorageInfo().getTotalStorageSize(); + + DocumentStorageInfoProto documentStorageInfo = + storageInfoResult.getStorageInfo().getDocumentStorageInfo(); + int totalDocuments = + documentStorageInfo.getNumAliveDocuments() + + documentStorageInfo.getNumExpiredDocuments(); + + if (totalStorageSize == 0 || totalDocuments == 0) { + // Maybe we can exit early and also avoid a divide by 0 error. + return new StorageInfo.Builder().build(); + } + + // Accumulate stats across the package's namespaces. + int aliveDocuments = 0; + int expiredDocuments = 0; + int aliveNamespaces = 0; + List<NamespaceStorageInfoProto> namespaceStorageInfos = + documentStorageInfo.getNamespaceStorageInfoList(); + for (int i = 0; i < namespaceStorageInfos.size(); i++) { + NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfos.get(i); + // The namespace from icing lib is already the prefixed format + if (prefixedNamespaces.contains(namespaceStorageInfo.getNamespace())) { + if (namespaceStorageInfo.getNumAliveDocuments() > 0) { + aliveNamespaces++; + aliveDocuments += namespaceStorageInfo.getNumAliveDocuments(); + } + expiredDocuments += namespaceStorageInfo.getNumExpiredDocuments(); + } + } + int namespaceDocuments = aliveDocuments + expiredDocuments; + + // Since we don't have the exact size of all the documents, we do an estimation. Note + // that while the total storage takes into account schema, index, etc. in addition to + // documents, we'll only calculate the percentage based on number of documents a + // client has. + return new StorageInfo.Builder() + .setSizeBytes((long) (namespaceDocuments * 1.0 / totalDocuments * totalStorageSize)) + .setAliveDocumentsCount(aliveDocuments) + .setAliveNamespacesCount(aliveNamespaces) + .build(); + } + /** * Persists all update/delete requests to the disk. * @@ -937,7 +1120,7 @@ public final class AppSearchImpl implements Closeable { throwIfClosedLocked(); PersistToDiskResultProto persistToDiskResultProto = - mIcingSearchEngineLocked.persistToDisk(); + mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL); checkSuccess(persistToDiskResultProto.getStatus()); } finally { mReadWriteLock.writeLock().unlock(); @@ -1275,6 +1458,153 @@ public final class AppSearchImpl implements Closeable { .addAllTypePropertyMasks(prefixedTypePropertyMasks); } + /** + * Adds result groupings for each namespace in each package being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerPackagePerNamespaceResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Create a map for package+namespace to prefixedNamespaces. This is NOT necessarily the + // same as the list of namespaces. If one package has multiple databases, each with the same + // namespace, then those should be grouped together. + Map<String, List<String>> packageAndNamespaceToNamespaces = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + String packageName = getPackageName(prefix); + // Create a new prefix without the database name. This will allow us to group namespaces + // that have the same name and package but a different database name together. + String emptyDatabasePrefix = createPrefix(packageName, /*databaseName*/ ""); + for (String prefixedNamespace : prefixedNamespaces) { + String namespace; + try { + namespace = removePrefix(prefixedNamespace); + } catch (AppSearchException e) { + // This should never happen. Skip this namespace if it does. + Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); + continue; + } + String emptyDatabasePrefixedNamespace = emptyDatabasePrefix + namespace; + List<String> namespaceList = + packageAndNamespaceToNamespaces.get(emptyDatabasePrefixedNamespace); + if (namespaceList == null) { + namespaceList = new ArrayList<>(); + packageAndNamespaceToNamespaces.put( + emptyDatabasePrefixedNamespace, namespaceList); + } + namespaceList.add(prefixedNamespace); + } + } + + for (List<String> namespaces : packageAndNamespaceToNamespaces.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(namespaces) + .setMaxResults(maxNumResults)); + } + } + + /** + * Adds result groupings for each package being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerPackageResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Build up a map of package to namespaces. + Map<String, List<String>> packageToNamespacesMap = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + String packageName = getPackageName(prefix); + List<String> packageNamespaceList = packageToNamespacesMap.get(packageName); + if (packageNamespaceList == null) { + packageNamespaceList = new ArrayList<>(); + packageToNamespacesMap.put(packageName, packageNamespaceList); + } + packageNamespaceList.addAll(prefixedNamespaces); + } + + for (List<String> prefixedNamespaces : packageToNamespacesMap.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(prefixedNamespaces) + .setMaxResults(maxNumResults)); + } + } + + /** + * Adds result groupings for each namespace being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerNamespaceResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Create a map of namespace to prefixedNamespaces. This is NOT necessarily the + // same as the list of namespaces. If a namespace exists under different packages and/or + // different databases, they should still be grouped together. + Map<String, List<String>> namespaceToPrefixedNamespaces = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + for (String prefixedNamespace : prefixedNamespaces) { + String namespace; + try { + namespace = removePrefix(prefixedNamespace); + } catch (AppSearchException e) { + // This should never happen. Skip this namespace if it does. + Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); + continue; + } + List<String> groupedPrefixedNamespaces = + namespaceToPrefixedNamespaces.get(namespace); + if (groupedPrefixedNamespaces == null) { + groupedPrefixedNamespaces = new ArrayList<>(); + namespaceToPrefixedNamespaces.put(namespace, groupedPrefixedNamespaces); + } + groupedPrefixedNamespaces.add(prefixedNamespace); + } + } + + for (List<String> namespaces : namespaceToPrefixedNamespaces.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(namespaces) + .setMaxResults(maxNumResults)); + } + } + @VisibleForTesting @GuardedBy("mReadWriteLock") SchemaProto getSchemaProtoLocked() throws AppSearchException { 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 ce1c9f4d4744..800b073fd44f 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 @@ -46,12 +46,13 @@ public final class SchemaToProtoConverter { * SchemaTypeConfigProto}. */ @NonNull - public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema) { + public static SchemaTypeConfigProto toSchemaTypeConfigProto( + @NonNull AppSearchSchema schema, int version) { Preconditions.checkNotNull(schema); SchemaTypeConfigProto.Builder protoBuilder = SchemaTypeConfigProto.newBuilder() .setSchemaType(schema.getSchemaType()) - .setVersion(schema.getVersion()); + .setVersion(version); List<AppSearchSchema.PropertyConfig> properties = schema.getProperties(); for (int i = 0; i < properties.size(); i++) { PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i)); @@ -116,8 +117,7 @@ public final class SchemaToProtoConverter { @NonNull public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) { Preconditions.checkNotNull(proto); - AppSearchSchema.Builder builder = - new AppSearchSchema.Builder(proto.getSchemaType()).setVersion(proto.getVersion()); + AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType()); List<PropertyConfigProto> properties = proto.getPropertiesList(); for (int i = 0; i < properties.size(); i++) { AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i)); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java index 1d8db7233a7a..bf7e5334d090 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java @@ -87,7 +87,9 @@ public class SearchResultToProtoConverter { GenericDocument document = GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument()); SearchResult.Builder builder = - new SearchResult.Builder(packageName, databaseName).setGenericDocument(document); + new SearchResult.Builder(packageName, databaseName) + .setGenericDocument(document) + .setRankingSignal(proto.getScore()); if (proto.hasSnippet()) { for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) { SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i); 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 3b5e27573aaa..d9e8adb52e5e 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 @@ -104,6 +104,10 @@ public final class SearchSpecToProtoConverter { return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT; case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP: return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP; + case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_COUNT; + case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_LAST_USED_TIMESTAMP; default: throw new IllegalArgumentException( "Invalid result ranking strategy: " + rankingStrategyCode); diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 09522152cefb..58f430b5bd61 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -I723a9d7b5e64329ab25b6d7627f3b2d222c31ac7 +Ie11a0555775a0ab2a39f6ce6d0d8a7b735c416ce 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 9ef6e0b2dee8..f0de4962ad3c 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 @@ -20,12 +20,12 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; -import android.app.appsearch.AppSearchSchema; import android.app.appsearch.AppSearchSession; import android.app.appsearch.AppSearchSessionShim; import android.app.appsearch.BatchResultCallback; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByUriRequest; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByUriRequest; import android.app.appsearch.ReportUsageRequest; @@ -34,6 +34,7 @@ import android.app.appsearch.SearchResultsShim; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; @@ -88,18 +89,26 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @NonNull public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) { SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create(); - mAppSearchSession.setSchema(request, mExecutor, future::set); + mAppSearchSession.setSchema(request, mExecutor, mExecutor, future::set); return Futures.transformAsync(future, this::transformResult, mExecutor); } @Override @NonNull - public ListenableFuture<Set<AppSearchSchema>> getSchema() { - SettableFuture<AppSearchResult<Set<AppSearchSchema>>> future = SettableFuture.create(); + public ListenableFuture<GetSchemaResponse> getSchema() { + SettableFuture<AppSearchResult<GetSchemaResponse>> future = SettableFuture.create(); mAppSearchSession.getSchema(mExecutor, future::set); return Futures.transformAsync(future, this::transformResult, mExecutor); } + @NonNull + @Override + public ListenableFuture<Set<String>> getNamespaces() { + SettableFuture<AppSearchResult<Set<String>>> future = SettableFuture.create(); + mAppSearchSession.getNamespaces(mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override @NonNull public ListenableFuture<AppSearchBatchResult<String, Void>> put( @@ -154,6 +163,14 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { return Futures.transformAsync(future, this::transformResult, mExecutor); } + @NonNull + @Override + public ListenableFuture<StorageInfo> getStorageInfo() { + SettableFuture<AppSearchResult<StorageInfo>> future = SettableFuture.create(); + mAppSearchSession.getStorageInfo(mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override public void close() { mAppSearchSession.close(); diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 69a4c18c4028..5042ce0efbd8 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -21,9 +21,11 @@ import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.GlobalSearchSession; import android.app.appsearch.GlobalSearchSessionShim; +import android.app.appsearch.ReportSystemUsageRequest; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchResultsShim; import android.app.appsearch.SearchSpec; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import androidx.test.core.app.ApplicationProvider; @@ -79,8 +81,24 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { return new SearchResultsShimImpl(searchResults, mExecutor); } + @NonNull + @Override + public ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request) { + SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); + mGlobalSearchSession.reportSystemUsage(request, mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override public void close() { mGlobalSearchSession.close(); } + + private <T> ListenableFuture<T> transformResult( + @NonNull AppSearchResult<T> result) throws AppSearchException { + if (!result.isSuccess()) { + throw new AppSearchException(result.getResultCode(), result.getErrorMessage()); + } + return Futures.immediateFuture(result.getResultValue()); + } } 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 1428fb1d3c7a..206904372236 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 @@ -52,12 +52,20 @@ public interface AppSearchSessionShim extends Closeable { /** * Retrieves the schema most recently successfully provided to {@link #setSchema}. * - * @return The pending result of performing this operation. + * @return The pending {@link GetSchemaResponse} of performing this operation. */ // This call hits disk; async API prevents us from treating these calls as properties. @SuppressLint("KotlinPropertyAccess") @NonNull - ListenableFuture<Set<AppSearchSchema>> getSchema(); + ListenableFuture<GetSchemaResponse> getSchema(); + + /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @return The pending result of performing this operation. + */ + @NonNull + ListenableFuture<Set<String>> getNamespaces(); /** * Indexes documents into the {@link AppSearchSessionShim} database. @@ -214,6 +222,17 @@ public interface AppSearchSessionShim extends Closeable { ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); /** + * Gets the storage info for this {@link AppSearchSessionShim} database. + * + * <p>This may take time proportional to the number of documents and may be inefficient to call + * repeatedly. + * + * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object. + */ + @NonNull + ListenableFuture<StorageInfo> getStorageInfo(); + + /** * Flush all schema and document updates, additions, and deletes to disk if possible. * * @return The pending result of performing this operation. {@link 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 4a3c7a53d43e..fd4734cea65f 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 @@ -73,14 +73,22 @@ public class AppSearchTestUtils { public static List<GenericDocument> convertSearchResultsToDocuments( SearchResultsShim searchResults) throws Exception { - List<SearchResult> results = searchResults.getNextPage().get(); - List<GenericDocument> documents = new ArrayList<>(); - while (results.size() > 0) { - for (SearchResult result : results) { - documents.add(result.getGenericDocument()); - } - results = searchResults.getNextPage().get(); + List<SearchResult> results = retrieveAllSearchResults(searchResults); + List<GenericDocument> documents = new ArrayList<>(results.size()); + for (SearchResult result : results) { + documents.add(result.getGenericDocument()); } return documents; } + + public static List<SearchResult> retrieveAllSearchResults(SearchResultsShim searchResults) + throws Exception { + List<SearchResult> page = searchResults.getNextPage().get(); + List<SearchResult> results = new ArrayList<>(); + while (!page.isEmpty()) { + results.addAll(page); + page = searchResults.getNextPage().get(); + } + return results; + } } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java index 440050faaa7d..f39916ee1510 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java @@ -18,6 +18,8 @@ package android.app.appsearch; import android.annotation.NonNull; +import com.google.common.util.concurrent.ListenableFuture; + import java.io.Closeable; /** @@ -51,6 +53,26 @@ public interface GlobalSearchSessionShim extends Closeable { @NonNull SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); + /** + * Reports that a particular document has been used from a system surface. + * + * <p>See {@link AppSearchSessionShim#reportUsage} for a general description of document usage, + * as well as an API that can be used by the app itself. + * + * <p>Usage reported via this method is accounted separately from usage reported via {@link + * AppSearchSessionShim#reportUsage} and may be accessed using the constants {@link + * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and {@link + * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}. + * + * @return The pending result of performing this operation which resolves to {@code null} on + * success. The pending result will be completed with an {@link + * android.app.appsearch.exceptions.AppSearchException} with a code of {@link + * AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an app which is not part + * of the system. + */ + @NonNull + ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request); + /** Closes the {@link GlobalSearchSessionShim}. */ @Override void close(); diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt index 80698f7cedc9..bebf0190191b 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -69,10 +69,12 @@ package android.media { method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException; method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...); method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer); + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); method @NonNull public String getParserName(); method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat); method public void release(); method public void seek(@NonNull android.media.MediaParser.SeekPoint); + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object); method public boolean supportsParameter(@NonNull String); field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking"; diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 8bdca766e0dd..cff422d0aafe 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.media.MediaCodec.CryptoInfo; +import android.media.metrics.LogSessionId; import android.os.Build; import android.text.TextUtils; import android.util.Log; @@ -74,6 +75,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; @@ -1066,6 +1068,7 @@ public final class MediaParser { private boolean mReleased; // MediaMetrics fields. + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; private final boolean mCreatedByName; private final SparseArray<Format> mTrackFormats; private String mLastObservedExceptionName; @@ -1328,6 +1331,7 @@ public final class MediaParser { MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH)); nativeSubmitMetrics( + // TODO: mLogSessionId, mParserName, mCreatedByName, String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool), @@ -1341,6 +1345,15 @@ public final class MediaParser { videoHeight); } + public void setLogSessionId(@NonNull LogSessionId sessionId) { + this.mLogSessionId = Objects.requireNonNull(sessionId); + } + + @NonNull + public LogSessionId getLogSessionId() { + return mLogSessionId; + } + // Private methods. private MediaParser( @@ -2184,6 +2197,7 @@ public final class MediaParser { // Native methods. private native void nativeSubmitMetrics( + // TODO: String logSessionId, String parserName, boolean createdByName, String parserPool, diff --git a/core/api/current.txt b/core/api/current.txt index 7fe282a5843b..feec08781852 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6320,13 +6320,13 @@ package android.app { method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int); method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle); method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int); - method @NonNull public String getCreatorPackage(); + method @Nullable public String getCreatorPackage(); method public int getCreatorUid(); method @NonNull public android.os.UserHandle getCreatorUserHandle(); method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int); method @NonNull public android.content.IntentSender getIntentSender(); method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int); - method @Deprecated @NonNull public String getTargetPackage(); + method @Deprecated @Nullable public String getTargetPackage(); method public boolean isActivity(); method public boolean isBroadcast(); method public boolean isForegroundService(); @@ -20278,6 +20278,7 @@ package android.media { method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices(); method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice(); method public android.media.AudioDeviceInfo[] getDevices(int); + method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public int getEncodedSurroundMode(); method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException; method public int getMode(); method public String getParameters(String); @@ -20300,6 +20301,7 @@ package android.media { method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes); method public boolean isSpeakerphoneOn(); method public boolean isStreamMute(int); + method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean isSurroundFormatEnabled(int); method public boolean isVolumeFixed(); method @Deprecated public boolean isWiredHeadsetOn(); method public void loadSoundEffects(); @@ -20319,6 +20321,7 @@ package android.media { method @Deprecated public void setBluetoothA2dpOn(boolean); method public void setBluetoothScoOn(boolean); method public boolean setCommunicationDevice(@NonNull android.media.AudioDeviceInfo); + method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setEncodedSurroundMode(int); method public void setMicrophoneMute(boolean); method public void setMode(int); method public void setParameters(String); @@ -20328,6 +20331,7 @@ package android.media { method @Deprecated public void setStreamMute(int, boolean); method @Deprecated public void setStreamSolo(int, boolean); method public void setStreamVolume(int, int, int); + method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setSurroundFormatEnabled(int, boolean); method @Deprecated public void setVibrateSetting(int, int); method @Deprecated public void setWiredHeadsetOn(boolean); method @Deprecated public boolean shouldVibrate(int); @@ -20366,6 +20370,10 @@ package android.media { field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0 field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1 field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0 + field public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; // 0x2 + field public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; // 0x0 + field public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; // 0x3 + field public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; // 0x1 field public static final int ERROR = -1; // 0xffffffff field public static final int ERROR_DEAD_OBJECT = -6; // 0xfffffffa field public static final String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.AUDIO_PLUG_STATE"; @@ -20579,6 +20587,7 @@ package android.media { method public int getChannelConfiguration(); method public int getChannelCount(); method @NonNull public android.media.AudioFormat getFormat(); + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); method public android.os.PersistableBundle getMetrics(); method public static int getMinBufferSize(int, int, int); method public int getNotificationMarkerPosition(); @@ -20601,6 +20610,7 @@ package android.media { method public void release(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener); + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); method public int setNotificationMarkerPosition(int); method public int setPositionNotificationPeriod(int); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); @@ -20716,6 +20726,7 @@ package android.media { method public int getChannelCount(); method public int getDualMonoMode(); method @NonNull public android.media.AudioFormat getFormat(); + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); method public static float getMaxVolume(); method public android.os.PersistableBundle getMetrics(); method public static int getMinBufferSize(int, int, int); @@ -20753,6 +20764,7 @@ package android.media { method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float); method public int setBufferSizeInFrames(@IntRange(from=0) int); method public boolean setDualMonoMode(int); + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int); method public int setNotificationMarkerPosition(int); method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int); @@ -21307,7 +21319,7 @@ package android.media { method @NonNull public String getDiagnosticInfo(); } - public final class MediaCodec implements android.media.metrics.PlaybackComponent { + public final class MediaCodec { method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int); method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler); method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException; @@ -21333,7 +21345,6 @@ package android.media { method @NonNull public android.media.MediaFormat getOutputFormat(int); method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int); method @Nullable public android.media.Image getOutputImage(int); - method public String getPlaybackId(); method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int); method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer); method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; @@ -21350,7 +21361,6 @@ package android.media { method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler); method public void setOutputSurface(@NonNull android.view.Surface); method public void setParameters(@Nullable android.os.Bundle); - method public void setPlaybackId(@NonNull String); method public void setVideoScalingMode(int); method public void signalEndOfInputStream(); method public void start(); @@ -21964,7 +21974,7 @@ package android.media { method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds(); method public int getOfflineLicenseState(@NonNull byte[]); method public int getOpenSessionCount(); - method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]); + method @Nullable public android.media.MediaDrm.PlaybackComponent getPlaybackComponent(@NonNull byte[]); method @NonNull public byte[] getPropertyByteArray(String); method @NonNull public String getPropertyString(@NonNull String); method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest(); @@ -22169,6 +22179,11 @@ package android.media { method public void onSessionLostState(@NonNull android.media.MediaDrm, @NonNull byte[]); } + public final class MediaDrm.PlaybackComponent { + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); + } + public static final class MediaDrm.ProvisionRequest { method @NonNull public byte[] getData(); method @NonNull public String getDefaultUrl(); @@ -22201,6 +22216,7 @@ package android.media { method public long getCachedDuration(); method public android.media.MediaExtractor.CasInfo getCasInfo(int); method public android.media.DrmInitData getDrmInitData(); + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); method public android.os.PersistableBundle getMetrics(); method @Nullable public java.util.Map<java.util.UUID,byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(@NonNull android.media.MediaCodec.CryptoInfo); @@ -22222,6 +22238,7 @@ package android.media { method public void setDataSource(@NonNull android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException; method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException; + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); method public void setMediaCas(@NonNull android.media.MediaCas); method public void unselectTrack(int); field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2 @@ -22825,6 +22842,7 @@ package android.media { method public java.util.List<android.media.MicrophoneInfo> getActiveMicrophones() throws java.io.IOException; method @Nullable public android.media.AudioRecordingConfiguration getActiveRecordingConfiguration(); method public static final int getAudioSourceMax(); + method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; method public android.os.PersistableBundle getMetrics(); method public android.media.AudioDeviceInfo getPreferredDevice(); @@ -22847,6 +22865,7 @@ package android.media { method public void setCaptureRate(double); method public void setInputSurface(@NonNull android.view.Surface); method public void setLocation(float, float); + method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); method public void setMaxDuration(int) throws java.lang.IllegalArgumentException; method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException; method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException; @@ -24478,6 +24497,8 @@ package android.media.metrics { } public final class LogSessionId { + method @NonNull public String getStringId(); + field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE; } public class MediaMetricsManager { @@ -24511,11 +24532,6 @@ package android.media.metrics { method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); } - public interface PlaybackComponent { - method @NonNull public String getPlaybackId(); - method public void setPlaybackId(@NonNull String); - } - public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable { method public int describeContents(); method public int getErrorCode(); @@ -34919,7 +34935,7 @@ package android.provider { field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; - field public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS = "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS"; + field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS"; field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; @@ -35551,6 +35567,8 @@ package android.provider { field public static final String AUTHORITY = "service-state"; field public static final android.net.Uri CONTENT_URI; field public static final String DATA_NETWORK_TYPE = "data_network_type"; + field public static final String DATA_REG_STATE = "data_reg_state"; + field public static final String DUPLEX_MODE = "duplex_mode"; field public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection"; field public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric"; field public static final String VOICE_REG_STATE = "voice_reg_state"; @@ -42346,7 +42364,7 @@ package android.telephony { field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2 field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1 field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0 - field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4 + field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5; // 0x5 field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3 field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4 } @@ -48098,6 +48116,7 @@ package android.view { } public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { + ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method public int describeContents(); method public void release(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 8dc5c15d48ce..5925b7263cf5 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -201,14 +201,6 @@ package android.net { method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); - field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 - field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 - field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 - field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 - field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 - field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 - field public static final int BLOCKED_REASON_NONE = 0; // 0x0 - field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 } public static interface NetworkPolicyManager.NetworkPolicyCallback { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9bcfe11bfe32..6ba9478ffc62 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -92,6 +92,7 @@ package android { field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; + field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; @@ -311,6 +312,7 @@ package android { field public static final int hotwordDetectionService = 16844326; // 0x1010626 field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int minExtensionVersion = 16844305; // 0x1010611 + field public static final int playHomeTransitionSound = 16844358; // 0x1010646 field public static final int requiredSystemPropertyName = 16844133; // 0x1010565 field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566 field public static final int sdkVersion = 16844304; // 0x1010610 @@ -1838,7 +1840,7 @@ package android.app.usage { package android.apphibernation { - public final class AppHibernationManager { + public class AppHibernationManager { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String); @@ -4450,6 +4452,7 @@ package android.location { method public boolean hasLowPowerMode(); method public boolean hasMeasurementCorrections(); method public boolean hasMeasurementCorrectionsExcessPathLength(); + method public boolean hasMeasurementCorrectionsForDriving(); method public boolean hasMeasurementCorrectionsLosSats(); method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane(); method public boolean hasMeasurementCorrectionsReflectingPlane(); @@ -4465,6 +4468,7 @@ package android.location { method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrections(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsExcessPathLength(boolean); + method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsForDriving(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsLosSats(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsReflectingPlane(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrelationVectors(boolean); @@ -7783,6 +7787,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); + method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback); @@ -7795,6 +7800,7 @@ package android.net.wifi.nl80211 { method public boolean tearDownClientInterface(@NonNull String); method public boolean tearDownInterfaces(); method public boolean tearDownSoftApInterface(@NonNull String); + method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR"; field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1 field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0 @@ -7805,6 +7811,10 @@ package android.net.wifi.nl80211 { field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1 } + public static interface WifiNl80211Manager.CountryCodeChangeListener { + method public void onChanged(@NonNull String); + } + public static class WifiNl80211Manager.OemSecurityType { ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int); field public final int groupCipher; @@ -8679,9 +8689,15 @@ package android.os.storage { method @WorkerThread public void allocateBytes(@NonNull java.util.UUID, long, @RequiresPermission int) throws java.io.IOException; method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException; method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String); method public static boolean hasIsolatedStorage(); method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException; field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1 + field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4 + field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1 + field public static final int MOUNT_MODE_EXTERNAL_INSTALLER = 2; // 0x2 + field public static final int MOUNT_MODE_EXTERNAL_NONE = 0; // 0x0 + field public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = 3; // 0x3 field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2 field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1 field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0 @@ -8928,6 +8944,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot"; + field public static final String NAMESPACE_APPSEARCH = "appsearch"; field public static final String NAMESPACE_APP_COMPAT = "app_compat"; field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service"; @@ -9005,6 +9022,8 @@ package android.provider { method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri); field public static final String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; field public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; + field public static final String DOWNLOADS_PROVIDER_AUTHORITY = "downloads"; + field public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"; field public static final String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED"; } @@ -14280,13 +14299,9 @@ package android.view.translation { } public final class UiTranslationManager { - method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId); - method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId); - method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId); - method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId); } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 0c02c43b1084..3926e39fd29a 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -181,3 +181,14 @@ package android.telephony.data { } +package android.view.translation { + + public final class UiTranslationManager { + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); + } + +} + diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b61004bc1b47..97ad48c96829 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -233,6 +233,8 @@ package android.app { field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; + field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera"; + field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone"; field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier"; field public static final int OP_COARSE_LOCATION = 0; // 0x0 field public static final int OP_RECORD_AUDIO = 27; // 0x1b @@ -1478,7 +1480,7 @@ package android.media.audiopolicy { package android.media.metrics { public final class LogSessionId { - method @NonNull public String getStringId(); + ctor public LogSessionId(@NonNull String); } } @@ -1931,6 +1933,17 @@ package android.os.vibrator { package android.permission { + public final class PermGroupUsage { + ctor public PermGroupUsage(@NonNull String, int, @NonNull String, long, boolean, boolean, @Nullable CharSequence); + method @Nullable public CharSequence getAttribution(); + method public long getLastAccess(); + method @NonNull public String getPackageName(); + method @NonNull public String getPermGroupName(); + method public int getUid(); + method public boolean isActive(); + method public boolean isPhoneCall(); + } + public final class PermissionControllerManager { method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler); @@ -1945,6 +1958,10 @@ package android.permission { method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>); } + public final class PermissionManager { + method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); + } + } package android.print { @@ -2333,6 +2350,7 @@ package android.telephony { public class ServiceState implements android.os.Parcelable { method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo); method public int getDataNetworkType(); + method public int getDataRegState(); method public void setCdmaSystemAndNetworkId(int, int); method public void setCellBandwidths(int[]); method public void setChannelNumber(int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c1d8541311a2..7298d8735eab 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2328,7 +2328,7 @@ public final class ActivityThread extends ClientTransactionHandler } @UnsupportedAppUsage - final Handler getHandler() { + public Handler getHandler() { return mH; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index a6aa28effe00..7e4af1ad7952 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1561,12 +1561,14 @@ public class AppOpsManager { * * @hide */ + @TestApi public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone"; /** * Phone call is using camera * * @hide */ + @TestApi public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera"; /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 0358fe56203c..03e95fc3b6b9 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,10 +19,12 @@ package android.app; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.StrictMode.vmIncorrectContextUseEnabled; +import static android.view.WindowManager.LayoutParams.WindowType; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiContext; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.BroadcastReceiver; @@ -87,6 +89,8 @@ import android.util.Slog; import android.view.Display; import android.view.DisplayAdjustments; import android.view.autofill.AutofillManager.AutofillClient; +import android.window.WindowContext; +import android.window.WindowTokenClient; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; @@ -1995,8 +1999,8 @@ class ContextImpl extends Context { final String errorMessage = "Tried to access visual service " + SystemServiceRegistry.getSystemServiceClassName(name) + " from a non-visual Context:" + getOuterContext(); - final String message = "Visual services, such as WindowManager" - + "or LayoutInflater should be accessed from Activity or other visual " + final String message = "Visual services, such as WindowManager " + + "or LayoutInflater should be accessed from Activity or another visual " + "Context. Use an Activity or a Context created with " + "Context#createWindowContext(int, Bundle), which are adjusted to " + "the configuration and visual bounds of an area on screen."; @@ -2563,23 +2567,63 @@ class ContextImpl extends Context { @NonNull @Override - public WindowContext createWindowContext(int type, @NonNull Bundle options) { + public WindowContext createWindowContext(@WindowType int type, + @Nullable Bundle options) { if (getDisplay() == null) { - throw new UnsupportedOperationException("WindowContext can only be created from " - + "other visual contexts, such as Activity or one created with " - + "Context#createDisplayContext(Display)"); + throw new UnsupportedOperationException("Please call this API with context associated" + + " with a display instance, such as Activity or context created via" + + " Context#createDisplayContext(Display), or try to invoke" + + " Context#createWindowContext(Display, int, Bundle)"); } - return new WindowContext(this, type, options); + return createWindowContextInternal(getDisplay(), type, options); } @NonNull @Override - public WindowContext createWindowContext(@NonNull Display display, int type, - @NonNull Bundle options) { + public WindowContext createWindowContext(@NonNull Display display, @WindowType int type, + @Nullable Bundle options) { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - return new WindowContext(this, display, type, options); + return createWindowContextInternal(display, type, options); + } + + /** + * The internal implementation of {@link Context#createWindowContext(int, Bundle)} and + * {@link Context#createWindowContext(Display, int, Bundle)}. + * + * @param display The {@link Display} instance to be associated with. + * + * @see Context#createWindowContext(Display, int, Bundle) + * @see Context#createWindowContext(int, Bundle) + */ + private WindowContext createWindowContextInternal(@NonNull Display display, + @WindowType int type, @Nullable Bundle options) { + // Step 1. Create a WindowTokenClient to associate with the WindowContext's Resources + // instance and it will be later used to receive configuration updates from the + // server side. + final WindowTokenClient windowTokenClient = new WindowTokenClient(); + + // Step 2. Create the base context of the window context, it will also create a Resources + // associated with the WindowTokenClient and set the token to the base context. + final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display); + + // Step 3. Create a WindowContext instance and set it as the outer context of the base + // context to make the service obtained by #getSystemService(String) able to query + // the WindowContext's WindowManager instead of the default one. + final WindowContext windowContext = new WindowContext(windowContextBase, type, options); + windowContextBase.setOuterContext(windowContext); + + // Step 4. Attach the WindowContext to the WindowTokenClient. In this way, when there's a + // configuration update from the server side, the update will then apply to + // WindowContext's resources. + windowTokenClient.attachContext(windowContext); + + // Step 5. Register the window context's token to the server side to associate with a + // window manager node. + windowContext.registerWithServer(); + + return windowContext; } @NonNull @@ -2588,40 +2632,65 @@ class ContextImpl extends Context { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - final ContextImpl tokenContext = createBaseWindowContext(token, display); - tokenContext.setResources(createWindowContextResources()); + final ContextImpl tokenContext = createWindowContextBase(token, display); + tokenContext.setResources(createWindowContextResources(tokenContext)); return tokenContext; } - - ContextImpl createBaseWindowContext(IBinder token, Display display) { - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + /** + * Creates the base {@link Context} for UI context to associate with a non-{@link Activity} + * window. + * + * @param token The token to associate with {@link Resources} + * @param display The {@link Display} to associate with. + * + * @see #createWindowContext(Display, int, Bundle) + * @see #createTokenContext(IBinder, Display) + */ + @UiContext + ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { + ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, token, mUser, mFlags, mClassLoader, null); // Window contexts receive configurations directly from the server and as such do not // need to override their display in ResourcesManager. - context.mForceDisplayOverrideInResources = false; - context.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; - if (display != null) { - context.mDisplay = display; - } - return context; + baseContext.mForceDisplayOverrideInResources = false; + baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; + baseContext.mDisplay = display; + + final Resources windowContextResources = createWindowContextResources(baseContext); + baseContext.setResources(windowContextResources); + + return baseContext; } - Resources createWindowContextResources() { - final String resDir = mPackageInfo.getResDir(); - final String[] splitResDirs = mPackageInfo.getSplitResDirs(); - final String[] legacyOverlayDirs = mPackageInfo.getOverlayDirs(); - final String[] overlayPaths = mPackageInfo.getOverlayPaths(); - final String[] libDirs = mPackageInfo.getApplicationInfo().sharedLibraryFiles; - final int displayId = getDisplayId(); + /** + * Creates the {@link Resources} to associate with the {@link WindowContext}'s token. + * + * When there's a {@link Configuration} update, this Resources instance will be updated to match + * the new configuration. + * + * @see WindowTokenClient + * @see #getWindowContextToken() + */ + private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) { + final LoadedApk packageInfo = windowContextBase.mPackageInfo; + final ClassLoader classLoader = windowContextBase.mClassLoader; + final IBinder token = windowContextBase.getWindowContextToken(); + + final String resDir = packageInfo.getResDir(); + final String[] splitResDirs = packageInfo.getSplitResDirs(); + final String[] legacyOverlayDirs = packageInfo.getOverlayDirs(); + final String[] overlayPaths = packageInfo.getOverlayPaths(); + final String[] libDirs = packageInfo.getApplicationInfo().sharedLibraryFiles; + final int displayId = windowContextBase.getDisplayId(); final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? mPackageInfo.getCompatibilityInfo() + ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - final List<ResourcesLoader> loaders = mResources.getLoaders(); + final List<ResourcesLoader> loaders = windowContextBase.mResources.getLoaders(); - return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs, - legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */, - compatInfo, mClassLoader, loaders); + return windowContextBase.mResourcesManager.createBaseTokenResources(token, resDir, + splitResDirs, legacyOverlayDirs, overlayPaths, libDirs, displayId, + null /* overrideConfig */, compatInfo, classLoader, loaders); } @NonNull @@ -3114,6 +3183,14 @@ class ContextImpl extends Context { return result; } + @Override + public void destroy() { + // The final clean-up is to release BroadcastReceiver registrations. It is called in + // ActivityThread for Activity and Service. For the context, such as WindowContext, + // without lifecycle concept, it should be called once the context is released. + scheduleFinalCleanup(getClass().getName(), getOuterContext().getClass().getSimpleName()); + } + // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 11adc5a60fb3..f4b95420154b 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -1010,7 +1010,7 @@ public final class PendingIntent implements Parcelable { * @deprecated Renamed to {@link #getCreatorPackage()}. */ @Deprecated - @NonNull + @Nullable public String getTargetPackage() { return getCreatorPackage(); } @@ -1032,7 +1032,7 @@ public final class PendingIntent implements Parcelable { * * @return The package name of the PendingIntent. */ - @NonNull + @Nullable public String getCreatorPackage() { return getCachedInfo().getCreatorPackage(); } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index ac8d3a261ac6..74134e16a7aa 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -39,13 +39,13 @@ import android.os.IBinder; import android.os.Process; import android.os.Trace; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.DisplayAdjustments; +import android.window.WindowContext; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -61,7 +61,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.WeakHashMap; @@ -168,7 +167,7 @@ public class ResourcesManager { /** * Class containing the base configuration override and set of resources associated with an - * Activity or {@link WindowContext}. + * {@link Activity} or a {@link WindowContext}. */ private static class ActivityResources { /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 117df02a2d7c..d3534f9bb3c7 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -872,8 +872,7 @@ public class DevicePolicyManager { * * The name is displayed only during provisioning. * - * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} - * or {@link #ACTION_PROVISION_FINANCED_DEVICE} + * <p>Use in an intent with action {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java index de778488df03..a36da8816d60 100644 --- a/core/java/android/apphibernation/AppHibernationManager.java +++ b/core/java/android/apphibernation/AppHibernationManager.java @@ -33,7 +33,7 @@ import java.util.List; */ @SystemApi @SystemService(Context.APP_HIBERNATION_SERVICE) -public final class AppHibernationManager { +public class AppHibernationManager { private static final String TAG = "AppHibernationManager"; private final Context mContext; private final IAppHibernationService mIAppHibernationService; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 92ff640e33b0..45a8ffd3fb5c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -81,6 +81,7 @@ import android.view.WindowManager.LayoutParams.WindowType; import android.view.autofill.AutofillManager.AutofillClient; import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import android.view.textclassifier.TextClassificationManager; +import android.window.WindowContext; import com.android.internal.compat.IPlatformCompat; import com.android.internal.compat.IPlatformCompatNative; @@ -6793,4 +6794,15 @@ public abstract class Context { public boolean isUiContext() { throw new RuntimeException("Not implemented. Must override in a subclass."); } + + /** + * Called when a {@link Context} is going to be released. + * This method can be overridden to perform the final cleanups, such as release + * {@link BroadcastReceiver} registrations. + * + * @see WindowContext#destroy() + * + * @hide + */ + public void destroy() { } } diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS index d0d406a0c9e6..01b554a56720 100644 --- a/core/java/android/content/OWNERS +++ b/core/java/android/content/OWNERS @@ -8,3 +8,5 @@ per-file Intent.java = patb@google.com per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS +per-file ComponentCallbacksController = file:/services/core/java/com/android/server/wm/OWNERS +per-file ComponentCallbacksController = charlesccchen@google.com diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index feb58a30e519..0952b3e1233c 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -550,9 +550,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1; /** + * Bit in {@link #privateFlags} indicating whether a home sound effect should be played if the + * home app moves to front after the activity with this flag set. + * Set from the {@link android.R.attr#playHomeTransitionSound} attribute. + * @hide + */ + public static final int PRIVATE_FLAG_HOME_TRANSITION_SOUND = 0x2; + + /** * Options that have been set in the activity declaration in the manifest. * These include: - * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}. + * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}, + * {@link #PRIVATE_FLAG_HOME_TRANSITION_SOUND}. * @hide */ public int privateFlags; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index d99c4109e5ad..ff6aaad09d09 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -149,7 +149,10 @@ public class ParsedActivityUtils { | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa) | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa); - activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa); + activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, + R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa) + | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND, + R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa); activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT); activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index aed0823bd52f..ac4b7b7dc158 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -360,7 +360,7 @@ public class Resources { WeakReference<Theme> weakThemeRef = mThemeRefs.get(i); Theme theme = weakThemeRef != null ? weakThemeRef.get() : null; if (theme != null) { - theme.setImpl(mResourcesImpl.newThemeImpl(theme.getKey())); + theme.setNewResourcesImpl(mResourcesImpl); } } } @@ -1500,6 +1500,9 @@ public class Resources { * retrieve XML attributes with style and theme information applied. */ public final class Theme { + private final Object mLock = new Object(); + + @GuardedBy("mLock") @UnsupportedAppUsage private ResourcesImpl.ThemeImpl mThemeImpl; @@ -1507,7 +1510,15 @@ public class Resources { } void setImpl(ResourcesImpl.ThemeImpl impl) { - mThemeImpl = impl; + synchronized (mLock) { + mThemeImpl = impl; + } + } + + void setNewResourcesImpl(ResourcesImpl resImpl) { + synchronized (mLock) { + mThemeImpl = resImpl.newThemeImpl(mThemeImpl.getKey()); + } } /** @@ -1528,7 +1539,9 @@ public class Resources { * if not already defined in the theme. */ public void applyStyle(int resId, boolean force) { - mThemeImpl.applyStyle(resId, force); + synchronized (mLock) { + mThemeImpl.applyStyle(resId, force); + } } /** @@ -1541,7 +1554,11 @@ public class Resources { * @param other The existing Theme to copy from. */ public void setTo(Theme other) { - mThemeImpl.setTo(other.mThemeImpl); + synchronized (mLock) { + synchronized (other.mLock) { + mThemeImpl.setTo(other.mThemeImpl); + } + } } /** @@ -1566,7 +1583,9 @@ public class Resources { */ @NonNull public TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) { - return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0); + synchronized (mLock) { + return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0); + } } /** @@ -1594,7 +1613,9 @@ public class Resources { public TypedArray obtainStyledAttributes(@StyleRes int resId, @NonNull @StyleableRes int[] attrs) throws NotFoundException { - return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId); + synchronized (mLock) { + return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId); + } } /** @@ -1650,7 +1671,10 @@ public class Resources { public TypedArray obtainStyledAttributes(@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes); + synchronized (mLock) { + return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, + defStyleRes); + } } /** @@ -1671,7 +1695,9 @@ public class Resources { @NonNull @UnsupportedAppUsage public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) { - return mThemeImpl.resolveAttributes(this, values, attrs); + synchronized (mLock) { + return mThemeImpl.resolveAttributes(this, values, attrs); + } } /** @@ -1692,7 +1718,9 @@ public class Resources { * <var>outValue</var> is valid, else false. */ public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { - return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs); + synchronized (mLock) { + return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs); + } } /** @@ -1702,7 +1730,9 @@ public class Resources { * @hide */ public int[] getAllAttributes() { - return mThemeImpl.getAllAttributes(); + synchronized (mLock) { + return mThemeImpl.getAllAttributes(); + } } /** @@ -1738,7 +1768,9 @@ public class Resources { * @see ActivityInfo */ public @Config int getChangingConfigurations() { - return mThemeImpl.getChangingConfigurations(); + synchronized (mLock) { + return mThemeImpl.getChangingConfigurations(); + } } /** @@ -1749,23 +1781,31 @@ public class Resources { * @param prefix Text to prefix each line printed. */ public void dump(int priority, String tag, String prefix) { - mThemeImpl.dump(priority, tag, prefix); + synchronized (mLock) { + mThemeImpl.dump(priority, tag, prefix); + } } // Needed by layoutlib. /*package*/ long getNativeTheme() { - return mThemeImpl.getNativeTheme(); + synchronized (mLock) { + return mThemeImpl.getNativeTheme(); + } } /*package*/ int getAppliedStyleResId() { - return mThemeImpl.getAppliedStyleResId(); + synchronized (mLock) { + return mThemeImpl.getAppliedStyleResId(); + } } /** * @hide */ public ThemeKey getKey() { - return mThemeImpl.getKey(); + synchronized (mLock) { + return mThemeImpl.getKey(); + } } private String getResourceNameFromHexString(String hexString) { @@ -1781,7 +1821,9 @@ public class Resources { */ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true) public String[] getTheme() { - return mThemeImpl.getTheme(); + synchronized (mLock) { + return mThemeImpl.getTheme(); + } } /** @hide */ @@ -1800,7 +1842,9 @@ public class Resources { * {@link #applyStyle(int, boolean)}. */ public void rebase() { - mThemeImpl.rebase(); + synchronized (mLock) { + mThemeImpl.rebase(); + } } /** @@ -1862,12 +1906,14 @@ public class Resources { @NonNull public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr, @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) { - int[] stack = mThemeImpl.getAttributeResolutionStack( - defStyleAttr, defStyleRes, explicitStyleRes); - if (stack == null) { - return new int[0]; - } else { - return stack; + synchronized (mLock) { + int[] stack = mThemeImpl.getAttributeResolutionStack( + defStyleAttr, defStyleRes, explicitStyleRes); + if (stack == null) { + return new int[0]; + } else { + return stack; + } } } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index cbcdb3756a2c..553e11b46da5 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -1314,22 +1314,16 @@ public class ResourcesImpl { } void applyStyle(int resId, boolean force) { - synchronized (mKey) { - mAssets.applyStyleToTheme(mTheme, resId, force); - mThemeResId = resId; - mKey.append(resId, force); - } + mAssets.applyStyleToTheme(mTheme, resId, force); + mThemeResId = resId; + mKey.append(resId, force); } void setTo(ThemeImpl other) { - synchronized (mKey) { - synchronized (other.mKey) { - mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme); + mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme); - mThemeResId = other.mThemeResId; - mKey.setTo(other.getKey()); - } - } + mThemeResId = other.mThemeResId; + mKey.setTo(other.getKey()); } @NonNull @@ -1338,46 +1332,40 @@ public class ResourcesImpl { @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - synchronized (mKey) { - final int len = attrs.length; - final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - - // XXX note that for now we only work with compiled XML files. - // To support generic XML files we will need to manually parse - // out the attributes from the XML file (applying type information - // contained in the resources and such). - final XmlBlock.Parser parser = (XmlBlock.Parser) set; - mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, - array.mDataAddress, array.mIndicesAddress); - array.mTheme = wrapper; - array.mXml = parser; - return array; - } + final int len = attrs.length; + final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); + + // XXX note that for now we only work with compiled XML files. + // To support generic XML files we will need to manually parse + // out the attributes from the XML file (applying type information + // contained in the resources and such). + final XmlBlock.Parser parser = (XmlBlock.Parser) set; + mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, + array.mDataAddress, array.mIndicesAddress); + array.mTheme = wrapper; + array.mXml = parser; + return array; } @NonNull TypedArray resolveAttributes(@NonNull Resources.Theme wrapper, @NonNull int[] values, @NonNull int[] attrs) { - synchronized (mKey) { - final int len = attrs.length; - if (values == null || len != values.length) { - throw new IllegalArgumentException( - "Base attribute values must the same length as attrs"); - } - - final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); - array.mTheme = wrapper; - array.mXml = null; - return array; + final int len = attrs.length; + if (values == null || len != values.length) { + throw new IllegalArgumentException( + "Base attribute values must the same length as attrs"); } + + final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); + mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); + array.mTheme = wrapper; + array.mXml = null; + return array; } boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { - synchronized (mKey) { - return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); - } + return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); } int[] getAllAttributes() { @@ -1385,35 +1373,29 @@ public class ResourcesImpl { } @Config int getChangingConfigurations() { - synchronized (mKey) { - final @NativeConfig int nativeChangingConfig = - AssetManager.nativeThemeGetChangingConfigurations(mTheme); - return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); - } + final @NativeConfig int nativeChangingConfig = + AssetManager.nativeThemeGetChangingConfigurations(mTheme); + return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); } public void dump(int priority, String tag, String prefix) { - synchronized (mKey) { - mAssets.dumpTheme(mTheme, priority, tag, prefix); - } + mAssets.dumpTheme(mTheme, priority, tag, prefix); } String[] getTheme() { - synchronized (mKey) { - final int N = mKey.mCount; - final String[] themes = new String[N * 2]; - for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { - final int resId = mKey.mResId[j]; - final boolean forced = mKey.mForce[j]; - try { - themes[i] = getResourceName(resId); - } catch (NotFoundException e) { - themes[i] = Integer.toHexString(i); - } - themes[i + 1] = forced ? "forced" : "not forced"; + final int n = mKey.mCount; + final String[] themes = new String[n * 2]; + for (int i = 0, j = n - 1; i < themes.length; i += 2, --j) { + final int resId = mKey.mResId[j]; + final boolean forced = mKey.mForce[j]; + try { + themes[i] = getResourceName(resId); + } catch (NotFoundException e) { + themes[i] = Integer.toHexString(i); } - return themes; + themes[i + 1] = forced ? "forced" : "not forced"; } + return themes; } /** @@ -1422,15 +1404,13 @@ public class ResourcesImpl { * {@link #applyStyle(int, boolean)}. */ void rebase() { - synchronized (mKey) { - AssetManager.nativeThemeClear(mTheme); - - // Reapply the same styles in the same order. - for (int i = 0; i < mKey.mCount; i++) { - final int resId = mKey.mResId[i]; - final boolean force = mKey.mForce[i]; - mAssets.applyStyleToTheme(mTheme, resId, force); - } + AssetManager.nativeThemeClear(mTheme); + + // Reapply the same styles in the same order. + for (int i = 0; i < mKey.mCount; i++) { + final int resId = mKey.mResId[i]; + final boolean force = mKey.mForce[i]; + mAssets.applyStyleToTheme(mTheme, resId, force); } } @@ -1455,10 +1435,8 @@ public class ResourcesImpl { @Nullable public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr, @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) { - synchronized (mKey) { - return mAssets.getAttributeResolutionStack( - mTheme, defStyleAttr, defStyleRes, explicitStyleRes); - } + return mAssets.getAttributeResolutionStack( + mTheme, defStyleAttr, defStyleRes, explicitStyleRes); } } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 96e7d3bc75c0..799ea4a15bc3 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -18,6 +18,7 @@ package android.net; import static android.app.ActivityManager.procStateToString; import static android.content.pm.PackageManager.GET_SIGNATURES; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import android.annotation.IntDef; import android.annotation.NonNull; @@ -203,78 +204,6 @@ public class NetworkPolicyManager { }) public @interface SubscriptionOverrideMask {} - /** - * Flag to indicate that an app is not subject to any restrictions that could result in its - * network access blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_REASON_NONE = 0; - - /** - * Flag to indicate that an app is subject to Battery saver restrictions that would - * result in its network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0; - - /** - * Flag to indicate that an app is subject to Doze restrictions that would - * result in its network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_REASON_DOZE = 1 << 1; - - /** - * Flag to indicate that an app is subject to App Standby restrictions that would - * result in its network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2; - - /** - * Flag to indicate that an app is subject to Restricted mode restrictions that would - * result in its network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; - - /** - * Flag to indicate that an app is subject to Data saver restrictions that would - * result in its metered network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16; - - /** - * Flag to indicate that an app is subject to user restrictions that would - * result in its metered network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17; - - /** - * Flag to indicate that an app is subject to Device admin restrictions that would - * result in its metered network access being blocked. - * - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18; - /** @hide */ public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000; @@ -344,22 +273,6 @@ public class NetworkPolicyManager { /** @hide */ public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000; - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, prefix = {"BLOCKED_"}, value = { - BLOCKED_REASON_NONE, - BLOCKED_REASON_BATTERY_SAVER, - BLOCKED_REASON_DOZE, - BLOCKED_REASON_APP_STANDBY, - BLOCKED_REASON_RESTRICTED_MODE, - BLOCKED_METERED_REASON_DATA_SAVER, - BLOCKED_METERED_REASON_USER_RESTRICTED, - BLOCKED_METERED_REASON_ADMIN_DISABLED, - }) - public @interface BlockedReason {} - private final Context mContext; @UnsupportedAppUsage private INetworkPolicyManager mService; @@ -883,14 +796,15 @@ public class NetworkPolicyManager { * {@code BLOCKED_REASON_*} and/or {@code BLOCKED_METERED_REASON_*} constants. * * @param blockedReasons Value indicating the reasons for why the network access of an UID is - * blocked. If the value is equal to {@link #BLOCKED_REASON_NONE}, then + * blocked. If the value is equal to + * {@link ConnectivityManager#BLOCKED_REASON_NONE}, then * it indicates that an app's network access is not blocked. * @param meteredNetwork Value indicating whether the network is metered or not. * @return Whether network access is blocked or not. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static boolean isUidBlocked(@BlockedReason int blockedReasons, boolean meteredNetwork) { + public static boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) { if (blockedReasons == BLOCKED_REASON_NONE) { return false; } @@ -913,7 +827,7 @@ public class NetworkPolicyManager { */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull - public static String blockedReasonsToString(@BlockedReason int blockedReasons) { + public static String blockedReasonsToString(int blockedReasons) { return DebugUtils.flagsToString(NetworkPolicyManager.class, "BLOCKED_", blockedReasons); } @@ -977,7 +891,7 @@ public class NetworkPolicyManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {} + default void onUidBlockedReasonChanged(int uid, int blockedReasons) {} } /** @hide */ @@ -992,8 +906,7 @@ public class NetworkPolicyManager { } @Override - public void onBlockedReasonChanged(int uid, @BlockedReason int oldBlockedReasons, - @BlockedReason int newBlockedReasons) { + public void onBlockedReasonChanged(int uid, int oldBlockedReasons, int newBlockedReasons) { if (oldBlockedReasons != newBlockedReasons) { dispatchOnUidBlockedReasonChanged(mExecutor, mCallback, uid, newBlockedReasons); } @@ -1001,7 +914,7 @@ public class NetworkPolicyManager { } private static void dispatchOnUidBlockedReasonChanged(@Nullable Executor executor, - @NonNull NetworkPolicyCallback callback, int uid, @BlockedReason int blockedReasons) { + @NonNull NetworkPolicyCallback callback, int uid, int blockedReasons) { if (executor == null) { callback.onUidBlockedReasonChanged(uid, blockedReasons); } else { diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl index 4078b249218c..74c3ba44b69e 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl @@ -23,6 +23,6 @@ package android.net.netstats.provider; */ oneway interface INetworkStatsProvider { void onRequestStatsUpdate(int token); - void onSetLimit(String iface, long quotaBytes); void onSetAlert(long quotaBytes); + void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes); } diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl index bd336dd348fe..7eaa01e262fe 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl @@ -26,6 +26,6 @@ import android.net.NetworkStats; oneway interface INetworkStatsProviderCallback { void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats); void notifyAlertReached(); - void notifyLimitReached(); + void notifyWarningOrLimitReached(); void unregister(); } diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java index 7639d2244cfe..65b336ad6fce 100644 --- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java +++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java @@ -29,7 +29,8 @@ import android.os.RemoteException; @SystemApi public abstract class NetworkStatsProvider { /** - * A value used by {@link #onSetLimit} and {@link #onSetAlert} indicates there is no limit. + * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit} + * indicates there is no limit. */ public static final int QUOTA_UNLIMITED = -1; @@ -42,13 +43,13 @@ public abstract class NetworkStatsProvider { } @Override - public void onSetLimit(String iface, long quotaBytes) { - NetworkStatsProvider.this.onSetLimit(iface, quotaBytes); + public void onSetAlert(long quotaBytes) { + NetworkStatsProvider.this.onSetAlert(quotaBytes); } @Override - public void onSetAlert(long quotaBytes) { - NetworkStatsProvider.this.onSetAlert(quotaBytes); + public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) { + NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes); } }; @@ -145,11 +146,28 @@ public abstract class NetworkStatsProvider { } /** - * Notify system that the quota set by {@code onSetLimit} has been reached. + * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached. + * + * @hide + */ + // TODO: Expose as system API. + public void notifyWarningReached() { + try { + // Reuse the code path to notify warning reached with limit reached + // since framework handles them in the same way. + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@link #onSetLimit} or limit set by + * {@link #onSetWarningAndLimit} has been reached. */ public void notifyLimitReached() { try { - getProviderCallbackBinderOrThrow().notifyLimitReached(); + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -180,9 +198,35 @@ public abstract class NetworkStatsProvider { * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. */ + // TODO: deprecate this once onSetWarningAndLimit is ready. public abstract void onSetLimit(@NonNull String iface, long quotaBytes); /** + * Called by {@code NetworkStatsService} when setting the interface quotas for the specified + * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system + * will not call {@link #onSetLimit}. When this method is called, the implementation + * should behave as follows: + * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}. + * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}. + * + * @param iface the interface requiring the operation. + * @param warningBytes the warning defined as the number of bytes, starting from zero and + * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates + * there is no warning. + * @param limitBytes the limit defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. + * + * @hide + */ + // TODO: Expose as system API. + public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) { + // Backward compatibility for those who didn't override this function. + onSetLimit(iface, limitBytes); + } + + /** * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations * MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes * have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 6a3cb42ed75d..5b79f7311b6d 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -29,7 +29,7 @@ import android.os.ParcelUuid; */ interface IVcnManagementService { void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName); - void clearVcnConfig(in ParcelUuid subscriptionGroup); + void clearVcnConfig(in ParcelUuid subscriptionGroup, in String opPkgName); void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index c0189e2202d2..abd41dacdeb6 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -154,7 +154,7 @@ public class VcnManager { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); try { - mService.clearVcnConfig(subscriptionGroup); + mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new IOException(e); } catch (RemoteException e) { diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 81c38f84849e..9385402c3d72 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -202,4 +202,5 @@ interface IStorageManager { void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92; PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93; boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94; - } + int getExternalStorageMountMode(int uid, in String packageName) = 95; +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index cae20ed90aa1..81071682ab7e 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2124,6 +2124,52 @@ public class StorageManager { } } + + /** @hide */ + @IntDef(prefix = { "MOUNT_MODE_" }, value = { + MOUNT_MODE_EXTERNAL_NONE, + MOUNT_MODE_EXTERNAL_DEFAULT, + MOUNT_MODE_EXTERNAL_INSTALLER, + MOUNT_MODE_EXTERNAL_PASS_THROUGH, + MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE + }) + /** @hide */ + public @interface MountMode {} + + /** + * No external storage should be mounted. + * @hide + */ + @SystemApi + public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; + /** + * Default external storage should be mounted. + * @hide + */ + @SystemApi + public static final int MOUNT_MODE_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT; + /** + * Mount mode for package installers which should give them access to + * all obb dirs in addition to their package sandboxes + * @hide + */ + @SystemApi + public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER; + /** + * The lower file system should be bind mounted directly on external storage + * @hide + */ + @SystemApi + public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH; + + /** + * Use the regular scoped storage filesystem, but Android/ should be writable. + * Used to support the applications hosting DownloadManager and the MTP server. + * @hide + */ + @SystemApi + public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = + IVold.REMOUNT_MODE_ANDROID_WRITABLE; /** * Flag indicating that a disk space allocation request should operate in an * aggressive mode. This flag should only be rarely used in situations that @@ -2301,6 +2347,28 @@ public class StorageManager { } /** + * Returns the External Storage mount mode corresponding to the given uid and packageName. + * These mount modes specify different views and access levels for + * different apps on external storage. + * + * @params uid UID of the application + * @params packageName name of the package + * @return {@code MountMode} for the given uid and packageName. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) + @SystemApi + @MountMode + public int getExternalStorageMountMode(int uid, @NonNull String packageName) { + try { + return mStorageManager.getExternalStorageMountMode(uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Allocate the requested number of bytes for your application to use in the * given open file. This will cause the system to delete any cached files * necessary to satisfy your request. diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java index c94c0ffd4652..440d6f269646 100644 --- a/core/java/android/permission/PermGroupUsage.java +++ b/core/java/android/permission/PermGroupUsage.java @@ -18,6 +18,7 @@ package android.permission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; /** * Represents the usage of a permission group by an app. Supports package name, user, permission @@ -26,6 +27,7 @@ import android.annotation.Nullable; * * @hide */ +@TestApi public final class PermGroupUsage { private final String mPackageName; @@ -36,7 +38,19 @@ public final class PermGroupUsage { private final boolean mIsPhoneCall; private final CharSequence mAttribution; - PermGroupUsage(@NonNull String packageName, int uid, + /** + * + * @param packageName The package name of the using app + * @param uid The uid of the using app + * @param permGroupName The name of the permission group being used + * @param lastAccess The time of last access + * @param isActive Whether this is active + * @param isPhoneCall Whether this is a usage by the phone + * @param attribution An optional string attribution to show + * @hide + */ + @TestApi + public PermGroupUsage(@NonNull String packageName, int uid, @NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall, @Nullable CharSequence attribution) { this.mPackageName = packageName; @@ -48,30 +62,58 @@ public final class PermGroupUsage { this.mAttribution = attribution; } + /** + * @hide + */ + @TestApi public @NonNull String getPackageName() { return mPackageName; } + /** + * @hide + */ + @TestApi public int getUid() { return mUid; } + /** + * @hide + */ + @TestApi public @NonNull String getPermGroupName() { return mPermGroupName; } + /** + * @hide + */ + @TestApi public long getLastAccess() { return mLastAccess; } + /** + * @hide + */ + @TestApi public boolean isActive() { return mIsActive; } + /** + * @hide + */ + @TestApi public boolean isPhoneCall() { return mIsPhoneCall; } + /** + * @hide + */ + @TestApi public @Nullable CharSequence getAttribution() { return mAttribution; } @@ -80,6 +122,7 @@ public final class PermGroupUsage { public String toString() { return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: " - + mPermGroupName + ", isActive: " + mIsActive + ", attribution: " + mAttribution; + + mPermGroupName + ", lastAccess: " + mLastAccess + ", isActive: " + mIsActive + + ", attribution: " + mAttribution; } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 177e422e7851..7669586cfc1f 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityThread; @@ -851,6 +852,7 @@ public final class PermissionManager { * * @hide */ + @TestApi @NonNull @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) public List<PermGroupUsage> getIndicatorAppOpUsageData() { diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 921911bbf479..2d6fa3c77966 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -71,13 +71,10 @@ public class PermissionUsageHelper { private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"; /** How long after an access to show it as "recent" */ - private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms"; + private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms"; /** How long after an access to show it as "running" */ - private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms"; - - /** The name of the expected voice IME subtype */ - private static final String VOICE_IME_SUBTYPE = "voice"; + private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms"; private static final String SYSTEM_PKG = "android"; @@ -279,6 +276,10 @@ public class PermissionUsageHelper { opEntry.getAttributedOpEntries().get(attributionTag); long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); + if (attrOpEntry.isRunning()) { + lastAccessTime = now; + } + if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { continue; } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 7e404972d11a..31cf63c93c3f 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -108,6 +108,13 @@ public final class DeviceConfig { public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; /** + * Namespace for all AppSearch related features. + * @hide + */ + @SystemApi + public static final String NAMESPACE_APPSEARCH = "appsearch"; + + /** * Namespace for app standby configurations. * * @hide diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 0fea48442941..620fa6517ca5 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -236,10 +236,21 @@ public final class DocumentsContract { public static final String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; - /** {@hide} */ + /** + * External Storage Provider's authority string + * {@hide} + */ + @SystemApi public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"; + /** + * Download Manager's authority string + * {@hide} + */ + @SystemApi + public static final String DOWNLOADS_PROVIDER_AUTHORITY = Downloads.Impl.AUTHORITY; + /** {@hide} */ public static final String EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ca63fbd548b7..719c38392fb1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1068,8 +1068,8 @@ public final class Settings { * Output: Nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS = - "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS"; + public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = + "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS"; /** * Activity Action: Show screen for controlling which apps can draw on top of other apps. @@ -8523,6 +8523,15 @@ public final class Settings { "one_handed_tutorial_show_count"; /** + * Indicates whether transform is enabled. + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + public static final String TRANSFORM_ENABLED = "transform_enabled"; + + /** * The current night mode that has been selected by the user. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 73208d35566e..38945f5bcea4 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4559,6 +4559,15 @@ public final class Telephony { public static final String VOICE_REG_STATE = "voice_reg_state"; /** + * An integer value indicating the current data service state. + * <p> + * Valid values: {@link ServiceState#STATE_IN_SERVICE}, + * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY}, + * {@link ServiceState#STATE_POWER_OFF}. + */ + public static final String DATA_REG_STATE = "data_reg_state"; + + /** * The current registered operator numeric id. * <p> * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit @@ -4581,6 +4590,17 @@ public final class Telephony { * This is the same as {@link TelephonyManager#getDataNetworkType()}. */ public static final String DATA_NETWORK_TYPE = "data_network_type"; + + /** + * An integer value indicating the current duplex mode if the radio technology is LTE, + * LTE-CA or NR. + * <p> + * Valid values: {@link ServiceState#DUPLEX_MODE_UNKNOWN}, + * {@link ServiceState#DUPLEX_MODE_FDD}, {@link ServiceState#DUPLEX_MODE_TDD}. + * <p> + * This is the same as {@link ServiceState#getDuplexMode()}. + */ + public static final String DUPLEX_MODE = "duplex_mode"; } /** diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a42126f18357..e1f13f2a93c7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -777,11 +777,11 @@ interface IWindowManager VerifiedDisplayHash verifyDisplayHash(in DisplayHash displayHash); /** - * Registers a listener for a {@link android.app.WindowContext} to handle configuration changes - * from the server side. + * Registers a listener for a {@link android.window.WindowContext} to handle configuration + * changes from the server side. * <p> * Note that this API should be invoked after calling - * {@link android.app.WindowTokenClient#attachContext(WindowContext)} + * {@link android.window.WindowTokenClient#attachContext(Context)} * </p> * * @param clientToken the window context's token diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 870fd8cc4f5d..11b161ad3cb2 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -79,6 +79,26 @@ public class SurfaceControlViewHost { mInputToken = inputToken; } + /** + * Constructs a copy of {@code SurfacePackage} with an independent lifetime. + * + * The caller can use this to create an independent copy in situations where ownership of + * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a + * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is + * responsible for releasing this copy when its done. + * + * @param other {@code SurfacePackage} to create a copy of. + */ + public SurfacePackage(@NonNull SurfacePackage other) { + SurfaceControl otherSurfaceControl = other.mSurfaceControl; + if (otherSurfaceControl != null && otherSurfaceControl.isValid()) { + mSurfaceControl = new SurfaceControl(); + mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage"); + } + mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; + mInputToken = other.mInputToken; + } + private SurfacePackage(Parcel in) { mSurfaceControl = new SurfaceControl(); mSurfaceControl.readFromParcel(in); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 9df87dc79405..93c3cab5b787 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2942,7 +2942,7 @@ public interface WindowManager extends ViewManager { public IBinder token = null; /** - * The token of {@link android.app.WindowContext}. It is usually a + * The token of {@link android.window.WindowContext}. It is usually a * {@link android.app.WindowTokenClient} and is used for associating the params with an * existing node in the WindowManager hierarchy and getting the corresponding * {@link Configuration} and {@link android.content.res.Resources} values with updates diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 8dce852a2d62..2bed3119a301 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -36,6 +36,7 @@ import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.window.WindowContext; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; @@ -110,7 +111,7 @@ public final class WindowManagerImpl implements WindowManager { return new WindowManagerImpl(displayContext, mParentWindow, mWindowContextToken); } - /** Creates a {@link WindowManager} for a {@link android.app.WindowContext}. */ + /** Creates a {@link WindowManager} for a {@link WindowContext}. */ public static WindowManager createWindowContextWindowManager(Context context) { final IBinder clientToken = context.getWindowContextToken(); return new WindowManagerImpl(context, null /* parentWindow */, clientToken); diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index d96c5c82fedb..52e4e15cc9a0 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -51,7 +51,7 @@ public final class WindowMetrics { * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics(); * // Gets all excluding insets * final WindowInsets windowInsets = metrics.getWindowInsets(); - * Insets insets = windowInsets.getInsetsIgnoreVisibility(WindowInsets.Type.navigationBars() + * Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() * | WindowInsets.Type.displayCutout()); * * int insetsWidth = insets.right + insets.left; diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index 852ffe8303b1..62868acac756 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -127,10 +127,13 @@ public final class UiTranslationManager { * @param destSpec {@link TranslationSpec} for the translated data. * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated * @param taskId the Activity Task id which needs ui translation + * @deprecated Use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>, + * ActivityId)} instead. * * @hide + * @removed */ - // TODO, hide the APIs + @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @@ -193,10 +196,13 @@ public final class UiTranslationManager { * NOTE: Please use {@code finishTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * @deprecated Use {@code finishTranslation(ActivityId)} instead. * * @hide + * @removed + * */ - // TODO, hide the APIs + @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) @SystemApi public void finishTranslation(int taskId) { @@ -240,10 +246,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * @deprecated Use {@code pauseTranslation(ActivityId)} instead. * * @hide + * @removed */ - // TODO, hide the APIs + @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) @SystemApi public void pauseTranslation(int taskId) { @@ -287,10 +295,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * @deprecated Use {@code resumeTranslation(ActivityId)} instead. * * @hide + * @removed */ - // TODO, hide the APIs + @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) @SystemApi public void resumeTranslation(int taskId) { diff --git a/core/java/android/app/WindowContext.java b/core/java/android/window/WindowContext.java index d44918cf0379..375f4cf63c3b 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.app; +package android.window; import static android.view.WindowManagerImpl.createWindowContextWindowManager; @@ -27,11 +27,7 @@ import android.content.ContextWrapper; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; -import android.view.Display; -import android.view.IWindowManager; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -49,11 +45,11 @@ import java.lang.ref.Reference; @UiContext public class WindowContext extends ContextWrapper { private final WindowManager mWindowManager; - private final IWindowManager mWms; - private final WindowTokenClient mToken; - private boolean mListenerRegistered; + private final @WindowManager.LayoutParams.WindowType int mType; + private final @Nullable Bundle mOptions; private final ComponentCallbacksController mCallbacksController = new ComponentCallbacksController(); + private final WindowContextController mController; /** * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to @@ -64,47 +60,23 @@ public class WindowContext extends ContextWrapper { * @hide */ public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) { - this(base, null /* display */, type, options); - } - - /** - * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to - * the token. - * - * @param base Base {@link Context} for this new instance. - * @param display the {@link Display} to override. - * @param type Window type to be used with this context. - * @hide - */ - public WindowContext(@NonNull Context base, @Nullable Display display, int type, - @Nullable Bundle options) { - // Correct base context will be built once the token is resolved, so passing 'null' here. - super(null /* base */); - - mWms = WindowManagerGlobal.getWindowManagerService(); - mToken = new WindowTokenClient(); - - final ContextImpl contextImpl = createBaseWindowContext(base, mToken, display); - attachBaseContext(contextImpl); - contextImpl.setOuterContext(this); - - mToken.attachContext(this); + super(base); + mType = type; + mOptions = options; mWindowManager = createWindowContextWindowManager(this); + IBinder token = getWindowContextToken(); + mController = new WindowContextController(token); - try { - mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(), - options); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } Reference.reachabilityFence(this); } - private static ContextImpl createBaseWindowContext(Context outer, IBinder token, - Display display) { - final ContextImpl contextImpl = ContextImpl.getImpl(outer); - return contextImpl.createBaseWindowContext(token, display); + /** + * Registers this {@link WindowContext} with {@link com.android.server.wm.WindowManagerService} + * to receive configuration changes of the associated {@link WindowManager} node. + */ + public void registerWithServer() { + mController.registerListener(mType, getDisplayId(), mOptions); } @Override @@ -124,21 +96,15 @@ public class WindowContext extends ContextWrapper { /** Used for test to invoke because we can't invoke finalize directly. */ @VisibleForTesting public void release() { - if (mListenerRegistered) { - mListenerRegistered = false; - try { - mWms.unregisterWindowContextListener(mToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + mController.unregisterListenerIfNeeded(); destroy(); } - void destroy() { + @Override + public void destroy() { mCallbacksController.clearCallbacks(); - final ContextImpl impl = (ContextImpl) getBaseContext(); - impl.scheduleFinalCleanup(getClass().getName(), "WindowContext"); + // Called to the base ContextImpl to do final clean-up. + getBaseContext().destroy(); Reference.reachabilityFence(this); } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java new file mode 100644 index 000000000000..61434145290f --- /dev/null +++ b/core/java/android/window/WindowContextController.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.WindowManager.LayoutParams.WindowType; +import android.view.WindowManagerGlobal; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * The controller to manage {@link WindowContext} listener, such as registering and unregistering + * the listener. + * + * @hide + */ +public class WindowContextController { + private final IWindowManager mWms; + @VisibleForTesting + public boolean mListenerRegistered; + @NonNull + private final IBinder mToken; + + /** + * Window Context Controller constructor + * + * @param token The token to register to the window context listener. It is usually from + * {@link Context#getWindowContextToken()}. + */ + public WindowContextController(@NonNull IBinder token) { + mToken = token; + mWms = WindowManagerGlobal.getWindowManagerService(); + } + + /** Used for test only. DO NOT USE it in production code. */ + @VisibleForTesting + public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) { + mToken = token; + mWms = mockWms; + } + + /** + * Registers the {@code mToken} to the window context listener. + * + * @param type The window type of the {@link WindowContext} + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @param options The window context launched option + */ + public void registerListener(@WindowType int type, int displayId, @Nullable Bundle options) { + if (mListenerRegistered) { + throw new UnsupportedOperationException("A Window Context can only register a listener" + + " once."); + } + try { + mListenerRegistered = mWms.registerWindowContextListener(mToken, type, displayId, + options); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters the window context listener associated with the {@code mToken} if it has been + * registered. + */ + public void unregisterListenerIfNeeded() { + if (mListenerRegistered) { + try { + mWms.unregisterWindowContextListener(mToken); + mListenerRegistered = false; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 82cef072ad0f..b2fe4d9573f1 100644 --- a/core/java/android/app/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -13,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.app; +package android.window; import android.annotation.NonNull; +import android.app.ActivityThread; +import android.app.IWindowToken; +import android.app.ResourcesManager; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; @@ -25,18 +28,22 @@ import android.view.WindowManagerGlobal; import java.lang.ref.WeakReference; /** - * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from - * server when window token config is updated or when it is moved between displays, and update the - * resources associated with this token on the client side. This will make sure that - * {@link WindowContext} instances will have updated resources and configuration. + * This class is used to receive {@link Configuration} changes from the associated window manager + * node on the server side, and apply the change to the {@link Context#getResources() associated + * Resources} of the attached {@link Context}. It is also used as + * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}. + * + * @see WindowContext + * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle) + * * @hide */ public class WindowTokenClient extends IWindowToken.Stub { /** * Attached {@link Context} for this window token to update configuration and resources. - * Initialized by {@link #attachContext(WindowContext)}. + * Initialized by {@link #attachContext(Context)}. */ - private WeakReference<WindowContext> mContextRef = null; + private WeakReference<Context> mContextRef = null; private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); @@ -50,18 +57,16 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param context context to be attached * @throws IllegalStateException if attached context has already existed. */ - void attachContext(@NonNull WindowContext context) { + public void attachContext(@NonNull Context context) { if (mContextRef != null) { throw new IllegalStateException("Context is already attached."); } mContextRef = new WeakReference<>(context); - final ContextImpl impl = ContextImpl.getImpl(context); - impl.setResources(impl.createWindowContextResources()); } @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { - final WindowContext context = mContextRef.get(); + final Context context = mContextRef.get(); if (context == null) { return; } @@ -72,8 +77,10 @@ public class WindowTokenClient extends IWindowToken.Stub { if (displayChanged || configChanged) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); - ActivityThread.currentActivityThread().getHandler().post( - () -> context.dispatchConfigurationChanged(newConfig)); + if (context instanceof WindowContext) { + ActivityThread.currentActivityThread().getHandler().post( + () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig)); + } } if (displayChanged) { context.updateDisplay(newDisplayId); @@ -82,7 +89,7 @@ public class WindowTokenClient extends IWindowToken.Stub { @Override public void onWindowTokenRemoved() { - final WindowContext context = mContextRef.get(); + final Context context = mContextRef.get(); if (context != null) { context.destroy(); mContextRef.clear(); diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index dd837fc2194c..922f96e96765 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -194,12 +194,12 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_share_with_work_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_share_with_work_apps_explanation); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_access_work_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_access_work_apps_explanation); } } @@ -209,12 +209,12 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_share_with_personal_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_share_with_personal_apps_explanation); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_access_personal_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_access_personal_apps_explanation); } } diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 2464fc737a1f..a2f014c86ec1 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -205,7 +205,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_access_work_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_access_work_apps_explanation); } @@ -213,7 +213,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cant_access_personal_apps, + R.string.resolver_cross_profile_blocked, R.string.resolver_cant_access_personal_apps_explanation); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8a471350bb36..1d462bc5dd48 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1230,7 +1230,7 @@ android:description="@string/permdesc_answerPhoneCalls" android:protectionLevel="dangerous|runtime" /> - <!-- Allows a calling application which manages it own calls through the self-managed + <!-- Allows a calling application which manages its own calls through the self-managed {@link android.telecom.ConnectionService} APIs. See {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the self-managed ConnectionService APIs. @@ -4440,6 +4440,13 @@ <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to disable system sound effects when the user exits one of + the application's activities. + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.DISABLE_SYSTEM_SOUND_EFFECTS" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to provide remote displays. <p>Not for use by third-party applications.</p> @hide --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 38e8f83ff767..dc4f52e980ce 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2862,6 +2862,13 @@ {@link android.content.Context#sendBroadcast(Intent, String)} being used. Multiple tags can be specified separated by '|'. --> <attr name="attributionTags"/> + <!-- Specifies whether a home sound effect should be played if the home app moves to + front after an activity with this flag set to <code>true</code>. + <p>The default value of this attribute is <code>true</code>. + <p>Also note that home sounds are only played if the device supports home sounds, + usually TVs. + <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. --> + <attr name="playHomeTransitionSound" format="boolean"/> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 3259cafb4f7e..16feb4fc7dac 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3093,6 +3093,8 @@ <public name="suppressesSpellChecker" /> <public name="usesPermissionFlags" /> <public name="requestOptimizedExternalStorageAccess" /> + <!-- @hide @SystemApi --> + <public name="playHomeTransitionSound" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0b3c4052e204..e2974bce694c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2815,6 +2815,15 @@ <!-- Displayed to the user to inform them that an app has accessed clipboard data (pasted as in "copy and paste") [CHAR LIMIT=50] --> <string name="pasted_from_clipboard"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted from clipboard</string> + <!-- Displayed to the user to inform them that an app has accessed text from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] --> + <string name="pasted_text"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted text you copied</string> + + <!-- Displayed to the user to inform them that an app has accessed an image from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] --> + <string name="pasted_image"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted an image you copied</string> + + <!-- Displayed to the user to inform them that an app has accessed content from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] --> + <string name="pasted_content"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted content you copied</string> + <!-- Menu item displayed at the end of a menu to allow users to see another page worth of menu items. This is shown on any app's menu as long as the app has too many items in the menu.--> <string name="more_item_label">More</string> <!-- Prepended to the shortcut for a menu item to indicate that the user should hold the MENU button together with the shortcut to invoke the item. For example, if the shortcut to open a new tab in browser is MENU and B together, then this would be prepended to the letter "B" --> @@ -5626,30 +5635,24 @@ <!-- Accessibility label for the work tab button. [CHAR LIMIT=NONE] --> <string name="resolver_work_tab_accessibility">Work view</string> - <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with work apps. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_share_with_work_apps">Can\u2019t share this with work apps</string> + <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this content across profiles. [CHAR LIMIT=NONE] --> + <string name="resolver_cross_profile_blocked">Blocked by your IT admin</string> <!-- Error message. This text is explaining that the user's IT admin doesn't allow this specific content to be shared with apps in the work profile. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_share_with_work_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your work profile</string> + <string name="resolver_cant_share_with_work_apps_explanation">This content can\u2019t be shared with work apps</string> - <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a work app. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_access_work_apps">Can\u2019t open this with work apps</string> <!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their work profile. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_access_work_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your work profile</string> + <string name="resolver_cant_access_work_apps_explanation">This content can\u2019t be opened with work apps</string> - <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with personal apps. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_share_with_personal_apps">Can\u2019t share this with personal apps</string> <!-- Error message. This text is explaining that the user's IT admin doesn't allow them to share this specific content with apps in their personal profile. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_share_with_personal_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your personal profile</string> + <string name="resolver_cant_share_with_personal_apps_explanation">This content can\u2019t be shared with personal apps</string> - <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a personal app. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_access_personal_apps">Can\u2019t open this with personal apps</string> <!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their personal profile. [CHAR LIMIT=NONE] --> - <string name="resolver_cant_access_personal_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your personal profile</string> + <string name="resolver_cant_access_personal_apps_explanation">This content can\u2019t be opened with personal apps</string> <!-- Error message. This text lets the user know that they need to turn on work apps in order to share or open content. There's also a button a user can tap to turn on the apps. [CHAR LIMIT=NONE] --> <string name="resolver_turn_on_work_apps">Work profile is paused</string> <!-- Button text. This button turns on a user's work profile so they can access their work apps and data. [CHAR LIMIT=NONE] --> - <string name="resolver_switch_on_work">Turn on</string> + <string name="resolver_switch_on_work">Tap to turn on</string> <!-- Error message. This text lets the user know that their current work apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] --> <string name="resolver_no_work_apps_available_share">No work apps can support this content</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a704936c5ed4..01374b1870cf 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -555,6 +555,9 @@ <java-symbol type="string" name="paste_as_plain_text" /> <java-symbol type="string" name="pasted_from_app" /> <java-symbol type="string" name="pasted_from_clipboard" /> + <java-symbol type="string" name="pasted_text" /> + <java-symbol type="string" name="pasted_image" /> + <java-symbol type="string" name="pasted_content" /> <java-symbol type="string" name="replace" /> <java-symbol type="string" name="undo" /> <java-symbol type="string" name="redo" /> @@ -4092,13 +4095,10 @@ <java-symbol type="id" name="resolver_empty_state_container" /> <java-symbol type="id" name="button_bar_container" /> <java-symbol type="id" name="title_container" /> - <java-symbol type="string" name="resolver_cant_share_with_work_apps" /> + <java-symbol type="string" name="resolver_cross_profile_blocked" /> <java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" /> - <java-symbol type="string" name="resolver_cant_share_with_personal_apps" /> <java-symbol type="string" name="resolver_cant_share_with_personal_apps_explanation" /> - <java-symbol type="string" name="resolver_cant_access_work_apps" /> <java-symbol type="string" name="resolver_cant_access_work_apps_explanation" /> - <java-symbol type="string" name="resolver_cant_access_personal_apps" /> <java-symbol type="string" name="resolver_cant_access_personal_apps_explanation" /> <java-symbol type="string" name="resolver_turn_on_work_apps" /> <java-symbol type="string" name="resolver_no_work_apps_available_share" /> diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java index 7ef1d5e426cc..6d9e2ea5acab 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java @@ -51,7 +51,7 @@ public class AppSearchSessionUnitTest { CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture = new CompletableFuture<>(); mSearchSession.setSchema( - new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, + new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, mExecutor, schemaFuture::complete); schemaFuture.get().getResultValue(); diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index c61a4b538a44..0b945895ad7f 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -2,3 +2,5 @@ per-file AssetTest.java = file:/core/java/android/content/res/OWNERS per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS +per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS +per-file ComponentCallbacksControllerTest = charlesccchen@google.com diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java index 96df9dda23c7..39ea8af6fed9 100644 --- a/core/tests/coretests/src/android/view/WindowMetricsTest.java +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -73,17 +73,17 @@ public class WindowMetricsTest { // Check get metrics do not crash. WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics(); WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics(); - verifyMetricsSanity(currentMetrics, maxMetrics); + verifyMetricsValidity(currentMetrics, maxMetrics); mWm.removeViewImmediate(view); // Check get metrics do not crash. currentMetrics = mWm.getCurrentWindowMetrics(); maxMetrics = mWm.getMaximumWindowMetrics(); - verifyMetricsSanity(currentMetrics, maxMetrics); + verifyMetricsValidity(currentMetrics, maxMetrics); }, 0); } - private static void verifyMetricsSanity(WindowMetrics currentMetrics, + private static void verifyMetricsValidity(WindowMetrics currentMetrics, WindowMetrics maxMetrics) { Rect currentBounds = currentMetrics.getBounds(); Rect maxBounds = maxMetrics.getBounds(); diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java new file mode 100644 index 000000000000..e4fc19c0e58d --- /dev/null +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.os.Binder; +import android.platform.test.annotations.Presubmit; +import android.view.IWindowManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link WindowContextController} + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:WindowContextControllerTest + * + * <p>This test class is a part of Window Manager Service tests and specified in + * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowContextControllerTest { + private WindowContextController mController; + private IWindowManager mMockWms; + + @Before + public void setUp() throws Exception { + mMockWms = mock(IWindowManager.class); + mController = new WindowContextController(new Binder(), mMockWms); + + doReturn(true).when(mMockWms).registerWindowContextListener( + any(), anyInt(), anyInt(), any()); + } + + @Test(expected = UnsupportedOperationException.class) + public void testRegisterListenerTwiceThrowException() { + mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, + null /* options */); + mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, + null /* options */); + } + + @Test + public void testUnregisterListenerIfNeeded_NotRegisteredYet_DoNothing() throws Exception { + mController.unregisterListenerIfNeeded(); + + verify(mMockWms, never()).registerWindowContextListener(any(), anyInt(), anyInt(), any()); + } + + @Test + public void testRegisterAndUnRegisterListener() { + mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, + null /* options */); + + assertThat(mController.mListenerRegistered).isTrue(); + + mController.unregisterListenerIfNeeded(); + + assertThat(mController.mListenerRegistered).isFalse(); + } +} diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index 48b58c6c0a1c..614e7c1d6fa4 100644 --- a/core/tests/coretests/src/android/app/WindowContextTest.java +++ b/core/tests/coretests/src/android/window/WindowContextTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app; +package android.window; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -24,6 +24,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.app.Activity; +import android.app.EmptyActivity; +import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -142,8 +145,8 @@ public class WindowContextTest { * {@link WindowManager.LayoutParams#token}. * * The window context token should be overridden to - * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must - * not be removed regardless of the release of window context. + * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be + * removed regardless of release of window context. */ @Test public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease() diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 4c58ad3d7f8d..80d47a936e69 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1608,14 +1608,13 @@ public class ChooserActivityTest { onView(withId(R.id.contentPanel)) .perform(swipeUp()); - onView(withText(R.string.resolver_cant_share_with_work_apps)) + onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @Test public void testWorkTab_workProfileDisabled_emptyStateShown() { // enable the work tab feature flag - ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); int workProfileTargets = 4; List<ResolvedComponentInfo> personalResolvedComponentInfos = @@ -1627,6 +1626,7 @@ public class ChooserActivityTest { Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); + ResolverActivity.ENABLE_TABBED_VIEW = true; final ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); @@ -1686,7 +1686,7 @@ public class ChooserActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_cant_share_with_work_apps)) + onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @@ -2116,7 +2116,7 @@ public class ChooserActivityTest { onView(withId(R.id.contentPanel)) .perform(swipeUp()); - onView(withText(R.string.resolver_cant_access_work_apps)) + onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 7dc5a8b58f91..68287caa8bbd 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -650,7 +650,7 @@ public class ResolverActivityTest { onView(withId(R.id.contentPanel)) .perform(swipeUp()); - onView(withText(R.string.resolver_cant_access_work_apps)) + onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @@ -726,7 +726,7 @@ public class ResolverActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_cant_access_work_apps)) + onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 3708e151c372..34c66a4f4b82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -16,6 +16,8 @@ package com.android.wm.shell; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; + import android.annotation.UiContext; import android.app.ResourcesManager; import android.content.Context; @@ -34,6 +36,8 @@ import android.window.DisplayAreaOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.protolog.common.ProtoLog; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -44,14 +48,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName(); - // Display area info. mapped by displayIds. + /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */ private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>(); - // Display area leashes. mapped by displayIds. + /** Display area leashes, which is mapped by display IDs. */ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners = new SparseArray<>(); - + /** {@link DisplayAreaContext} list, which is mapped by display IDs. */ private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>(); private final Context mContext; @@ -173,8 +177,9 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { final Display display = mContext.getSystemService(DisplayManager.class) .getDisplay(displayId); if (display == null) { - throw new UnsupportedOperationException("The display #" + displayId + " is invalid." - + "displayAreaInfo:" + displayAreaInfo); + ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed." + + " Skip following steps", displayId); + return; } DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId); if (daContext == null) { diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java index a5e28158ab78..fdf0f59c13dc 100644 --- a/location/java/android/location/GnssCapabilities.java +++ b/location/java/android/location/GnssCapabilities.java @@ -61,6 +61,8 @@ public final class GnssCapabilities implements Parcelable { public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 4096; /** @hide */ public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 8192; + /** @hide */ + public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 16384; /** @hide */ @IntDef(flag = true, prefix = {"TOP_HAL_CAPABILITY_"}, value = {TOP_HAL_CAPABILITY_SCHEDULING, @@ -69,7 +71,8 @@ public final class GnssCapabilities implements Parcelable { TOP_HAL_CAPABILITY_MEASUREMENTS, TOP_HAL_CAPABILITY_NAV_MESSAGES, TOP_HAL_CAPABILITY_LOW_POWER_MODE, TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST, TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS, TOP_HAL_CAPABILITY_ANTENNA_INFO, - TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT}) + TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT, + TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING}) @Retention(RetentionPolicy.SOURCE) public @interface TopHalCapabilityFlags {} @@ -351,6 +354,17 @@ public final class GnssCapabilities implements Parcelable { } /** + * Returns {@code true} if GNSS chipset will benefit from measurement corrections for driving + * use case if provided, {@code false} otherwise. + * + * @hide + */ + @SystemApi + public boolean hasMeasurementCorrectionsForDriving() { + return (mTopFlags & TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING) != 0; + } + + /** * Returns {@code true} if GNSS chipset supports line-of-sight satellite identification * measurement corrections, {@code false} otherwise. * @@ -550,6 +564,9 @@ public final class GnssCapabilities implements Parcelable { if (hasMeasurementCorrelationVectors()) { builder.append("MEASUREMENT_CORRELATION_VECTORS "); } + if (hasMeasurementCorrectionsForDriving()) { + builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING "); + } if (hasMeasurementCorrectionsLosSats()) { builder.append("LOS_SATS "); } @@ -748,6 +765,18 @@ public final class GnssCapabilities implements Parcelable { } /** + * Sets measurement corrections for driving capability. + * + * @hide + */ + @SystemApi + public @NonNull Builder setHasMeasurementCorrectionsForDriving(boolean capable) { + mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING, + capable); + return this; + } + + /** * Sets measurement corrections line-of-sight satellites capabilitity. * * @hide diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f957a73144c8..9aeef07984fa 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -568,6 +568,42 @@ public class AudioManager { public static final int FLAG_FROM_KEY = 1 << 12; /** @hide */ + @IntDef(prefix = {"ENCODED_SURROUND_OUTPUT_"}, value = { + ENCODED_SURROUND_OUTPUT_AUTO, + ENCODED_SURROUND_OUTPUT_NEVER, + ENCODED_SURROUND_OUTPUT_ALWAYS, + ENCODED_SURROUND_OUTPUT_MANUAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncodedSurroundOutputMode {} + + /** + * The surround sound formats are available for use if they are detected. This is the default + * mode. + */ + public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; + + /** + * The surround sound formats are NEVER available, even if they are detected by the hardware. + * Those formats will not be reported. + */ + public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; + + /** + * The surround sound formats are ALWAYS available, even if they are not detected by the + * hardware. Those formats will be reported as part of the HDMI output capability. + * Applications are then free to use either PCM or encoded output. + */ + public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; + + /** + * Surround sound formats are available according to the choice of user, even if they are not + * detected by the hardware. Those formats will be reported as part of the HDMI output + * capability. Applications are then free to use either PCM or encoded output. + */ + public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; + + /** @hide */ @IntDef(flag = true, prefix = "FLAG", value = { FLAG_SHOW_UI, FLAG_ALLOW_RINGER_MODES, @@ -6754,6 +6790,34 @@ public class AudioManager { } /** + * Sets the surround sound mode. + * + * @return true if successful, otherwise false + */ + @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) + public boolean setEncodedSurroundMode(@EncodedSurroundOutputMode int mode) { + try { + return getService().setEncodedSurroundMode(mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the surround sound mode. + * + * @return true if successful, otherwise false + */ + @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) + public @EncodedSurroundOutputMode int getEncodedSurroundMode() { + try { + return getService().getEncodedSurroundMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide * Returns all surround formats. * @return a map where the key is a surround format and @@ -6771,7 +6835,6 @@ public class AudioManager { } /** - * @hide * Set a certain surround format as enabled or not. * @param audioFormat a surround format, the value is one of * {@link AudioFormat#ENCODING_AC3}, {@link AudioFormat#ENCODING_E_AC3}, @@ -6785,10 +6848,29 @@ public class AudioManager { * @param enabled the required surround format state, true for enabled, false for disabled * @return true if successful, otherwise false */ + @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setSurroundFormatEnabled( @AudioFormat.SurroundSoundEncoding int audioFormat, boolean enabled) { - int status = AudioSystem.setSurroundFormatEnabled(audioFormat, enabled); - return status == AudioManager.SUCCESS; + try { + return getService().setSurroundFormatEnabled(audioFormat, enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets whether a certain surround format is enabled or not. + * @param audioFormat a surround format + * + * @return whether the required surround format is enabled + */ + @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) + public boolean isSurroundFormatEnabled(@AudioFormat.SurroundSoundEncoding int audioFormat) { + try { + return getService().isSurroundFormatEnabled(audioFormat); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 0d613992f300..3399377be999 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -32,6 +32,7 @@ import android.content.Context; import android.media.MediaRecorder.Source; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioPolicy; +import android.media.metrics.LogSessionId; import android.media.permission.Identity; import android.media.projection.MediaProjection; import android.os.Binder; @@ -282,9 +283,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, /** * The log session id used for metrics. - * A null or empty string here means it is not set. + * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set. */ - private String mLogSessionId; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; //--------------------------------------------------------- // Constructor, Finalize @@ -1963,24 +1964,34 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, } /** - * Sets a string handle to this AudioRecord for metrics collection. + * Sets a {@link LogSessionId} instance to this AudioRecord for metrics collection. * - * @param logSessionId a string which is used to identify this object - * to the metrics service. Proper generated Ids must be obtained - * from the Java metrics service and should be considered opaque. - * Use null to remove the logSessionId association. + * @param logSessionId a {@link LogSessionId} instance which is used to + * identify this object to the metrics service. Proper generated + * Ids must be obtained from the Java metrics service and should + * be considered opaque. Use + * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the + * logSessionId association. * @throws IllegalStateException if AudioRecord not initialized. - * - * @hide */ - public void setLogSessionId(@Nullable String logSessionId) { + public void setLogSessionId(@NonNull LogSessionId logSessionId) { + Objects.requireNonNull(logSessionId); if (mState == STATE_UNINITIALIZED) { throw new IllegalStateException("AudioRecord not initialized"); } - native_setLogSessionId(logSessionId); + String stringId = logSessionId.getStringId(); + native_setLogSessionId(stringId); mLogSessionId = logSessionId; } + /** + * Returns the {@link LogSessionId}. + */ + @NonNull + public LogSessionId getLogSessionId() { + return mLogSessionId; + } + //--------------------------------------------------------- // Interface definitions //-------------------- diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index bccefdfd7b96..7a2b022b514a 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -26,6 +26,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.media.metrics.LogSessionId; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -45,6 +46,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.NioUtils; import java.util.LinkedList; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -567,9 +569,9 @@ public class AudioTrack extends PlayerBase /** * The log session id used for metrics. - * A null or empty string here means it is not set. + * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set. */ - private String mLogSessionId; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; //-------------------------------- // Used exclusively by native code @@ -4044,24 +4046,35 @@ public class AudioTrack extends PlayerBase } /** - * Sets a string handle to this AudioTrack for metrics collection. + * Sets a {@link LogSessionId} instance to this AudioTrack for metrics collection. * - * @param logSessionId a string which is used to identify this object - * to the metrics service. Proper generated Ids must be obtained - * from the Java metrics service and should be considered opaque. - * Use null to remove the logSessionId association. + * @param logSessionId a {@link LogSessionId} instance which is used to + * identify this object to the metrics service. Proper generated + * Ids must be obtained from the Java metrics service and should + * be considered opaque. Use + * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the + * logSessionId association. * @throws IllegalStateException if AudioTrack not initialized. * - * @hide */ - public void setLogSessionId(@Nullable String logSessionId) { + public void setLogSessionId(@NonNull LogSessionId logSessionId) { + Objects.requireNonNull(logSessionId); if (mState == STATE_UNINITIALIZED) { throw new IllegalStateException("track not initialized"); } - native_setLogSessionId(logSessionId); + String stringId = logSessionId.getStringId(); + native_setLogSessionId(stringId); mLogSessionId = logSessionId; } + /** + * Returns the {@link LogSessionId}. + */ + @NonNull + public LogSessionId getLogSessionId() { + return mLogSessionId; + } + //--------------------------------------------------------- // Inner classes //-------------------- diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 4f87fe6c5f8c..ee945d5a715f 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -158,6 +158,14 @@ interface IAudioService { oneway void reloadAudioSettings(); + boolean setSurroundFormatEnabled(int audioFormat, boolean enabled); + + boolean isSurroundFormatEnabled(int audioFormat); + + boolean setEncodedSurroundMode(int mode); + + int getEncodedSurroundMode(); + oneway void avrcpSupportsAbsoluteVolume(String address, boolean support); void setSpeakerphoneOn(IBinder cb, boolean on); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index a7e2b65ebb8f..8db75d6ec2b1 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -25,7 +25,6 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.HardwareBuffer; import android.media.MediaCodecInfo.CodecCapabilities; -import android.media.metrics.PlaybackComponent; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -1539,7 +1538,7 @@ import java.util.concurrent.locks.ReentrantLock; </tbody> </table> */ -final public class MediaCodec implements PlaybackComponent { +final public class MediaCodec { /** * Per buffer metadata includes an offset and size specifying @@ -1697,22 +1696,6 @@ final public class MediaCodec implements PlaybackComponent { private static final int CB_OUTPUT_FORMAT_CHANGE = 4; - /** - * @hide - */ - @Override - public void setPlaybackId(@NonNull String playbackId) { - // TODO: add a native method to pass the ID to the native code for logging. - mPlaybackId = playbackId; - } - /** - * @hide - */ - @Override - public String getPlaybackId() { - return mPlaybackId; - } - private class EventHandler extends Handler { private MediaCodec mCodec; diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index ae64c026fb51..10b99dce53b0 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -27,7 +27,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; -import android.media.metrics.PlaybackComponent; +import android.media.metrics.LogSessionId; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -50,6 +50,7 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -1379,7 +1380,7 @@ public final class MediaDrm implements AutoCloseable { public byte[] openSession(@SecurityLevel int level) throws NotProvisionedException, ResourceBusyException { byte[] sessionId = openSessionNative(level); - mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponentImpl(sessionId)); + mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId)); return sessionId; } @@ -2929,8 +2930,8 @@ public final class MediaDrm implements AutoCloseable { /** * Obtain a {@link PlaybackComponent} associated with a DRM session. - * Call {@link PlaybackComponent#setPlaybackId(String)} on the returned object - * to associate a playback session with the DRM session. + * Call {@link PlaybackComponent#setLogSessionId(LogSessionId)} on + * the returned object to associate a playback session with the DRM session. * * @param sessionId a DRM session ID obtained from {@link #openSession()} * @return a {@link PlaybackComponent} associated with the session, @@ -2945,28 +2946,37 @@ public final class MediaDrm implements AutoCloseable { return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId)); } - private native void setPlaybackId(byte[] sessionId, String playbackId); + private native void setPlaybackId(byte[] sessionId, String logSessionId); - private final class PlaybackComponentImpl implements PlaybackComponent { + /** This class contains the Drm session ID and log session ID */ + public final class PlaybackComponent { private final byte[] mSessionId; - private String mPlaybackId = ""; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; - public PlaybackComponentImpl(byte[] sessionId) { + /** @hide */ + public PlaybackComponent(byte[] sessionId) { mSessionId = sessionId; } - @Override - public void setPlaybackId(@NonNull String playbackId) { - if (playbackId == null) { + + /** + * Gets the {@link LogSessionId}. + */ + public void setLogSessionId(@NonNull LogSessionId logSessionId) { + Objects.requireNonNull(logSessionId); + if (logSessionId.getStringId() == null) { throw new IllegalArgumentException("playbackId is null"); } - MediaDrm.this.setPlaybackId(mSessionId, playbackId); - mPlaybackId = playbackId; + MediaDrm.this.setPlaybackId(mSessionId, logSessionId.getStringId()); + mLogSessionId = logSessionId; } - @Override - @NonNull public String getPlaybackId() { - return mPlaybackId; + + /** + * Returns the {@link LogSessionId}. + */ + @NonNull public LogSessionId getLogSessionId() { + return mLogSessionId; } } diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 8f603300dc11..283f1f1d69a2 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.media.metrics.LogSessionId; import android.net.Uri; import android.os.IBinder; import android.os.IHwBinder; @@ -40,6 +41,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -73,7 +75,7 @@ import java.util.stream.Collectors; * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission * when used with network-based content. */ -final public class MediaExtractor { +public final class MediaExtractor { public MediaExtractor() { native_setup(); } @@ -768,6 +770,22 @@ final public class MediaExtractor { public native boolean hasCacheReachedEndOfStream(); /** + * Sets the {@link LogSessionId} for MediaExtractor. + */ + public void setLogSessionId(@NonNull LogSessionId logSessionId) { + mLogSessionId = Objects.requireNonNull(logSessionId); + // TODO: implement native_setPlaybackId(playbackId); + } + + /** + * Returns the {@link LogSessionId} for MediaExtractor. + */ + @NonNull + public LogSessionId getLogSessionId() { + return mLogSessionId; + } + + /** * Return Metrics data about the current media container. * * @return a {@link PersistableBundle} containing the set of attributes and values @@ -796,6 +814,7 @@ final public class MediaExtractor { } private MediaCas mMediaCas; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; private long mNativeContext; diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index dd08d8adf20b..f960ff2b1a7e 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -29,6 +29,7 @@ import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.hardware.Camera; +import android.media.metrics.LogSessionId; import android.media.permission.Identity; import android.os.Build; import android.os.Handler; @@ -130,6 +131,8 @@ public class MediaRecorder implements AudioRouting, private int mChannelCount; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; + /** * Default constructor. * @@ -165,6 +168,27 @@ public class MediaRecorder implements AudioRouting, } /** + * Sets the {@link LogSessionId} for MediaRecorder. + * + * @param id the global ID for monitoring the MediaRecorder performance + */ + public void setLogSessionId(@NonNull LogSessionId id) { + Objects.requireNonNull(id); + mLogSessionId = id; + setParameter("log-session-id=" + id.getStringId()); + } + + /** + * Returns the {@link LogSessionId} for MediaRecorder. + * + * @return the global ID for monitoring the MediaRecorder performance + */ + @NonNull + public LogSessionId getLogSessionId() { + return mLogSessionId; + } + + /** * Sets a {@link android.hardware.Camera} to use for recording. * * <p>Use this function to switch quickly between preview and capture mode without a teardown of diff --git a/media/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java index f68ef4b68de1..41f30937489b 100644 --- a/media/java/android/media/metrics/LogSessionId.java +++ b/media/java/android/media/metrics/LogSessionId.java @@ -17,20 +17,29 @@ package android.media.metrics; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; +import java.util.Objects; + /** * An instances of this class represents the ID of a log session. */ public final class LogSessionId { - private final String mSessionId; + @NonNull private final String mSessionId; - /* package */ LogSessionId(@NonNull String id) { - mSessionId = id; - } + /** + * A {@link LogSessionId} object which is used to clear any existing session ID. + */ + @NonNull public static final LogSessionId LOG_SESSION_ID_NONE = new LogSessionId(""); /** @hide */ @TestApi + public LogSessionId(@NonNull String id) { + mSessionId = Objects.requireNonNull(id); + } + + /** Returns the ID represented by a string. */ @NonNull public String getStringId() { return mSessionId; @@ -40,4 +49,17 @@ public final class LogSessionId { public String toString() { return mSessionId; } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LogSessionId that = (LogSessionId) o; + return Objects.equals(mSessionId, that.mSessionId); + } + + @Override + public int hashCode() { + return Objects.hash(mSessionId); + } } diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java deleted file mode 100644 index 1cadf3be38ee..000000000000 --- a/media/java/android/media/metrics/PlaybackComponent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.metrics; - -import android.annotation.NonNull; - -/** - * Interface for playback related components used by playback metrics. - */ -public interface PlaybackComponent { - - /** - * Sets the playback ID of the component. - */ - void setPlaybackId(@NonNull String playbackId); - - /** - * Gets playback ID. - */ - @NonNull String getPlaybackId(); -} diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 6d7badb08905..35249f615e6b 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -258,6 +258,9 @@ LIBANDROID { ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29 ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29 ASurfaceTransaction_setOnComplete; # introduced=29 + ASurfaceTransaction_setPosition; # introduced=31 + ASurfaceTransaction_setSourceRect; # introduced=31 + ASurfaceTransaction_setTransform; # introduced=31 ASurfaceTransaction_setVisibility; # introduced=29 ASurfaceTransaction_setZOrder; # introduced=29 ASystemFontIterator_open; # introduced=29 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index e8cf63f64572..195fd5e60295 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -446,6 +446,44 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction, transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay); } +void ASurfaceTransaction_setSourceRect(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, const ARect& source) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + CHECK_VALID_RECT(source); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->setCrop(surfaceControl, static_cast<const Rect&>(source)); +} + +void ASurfaceTransaction_setPosition(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, const ARect& destination) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + CHECK_VALID_RECT(destination); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->setFrame(surfaceControl, static_cast<const Rect&>(destination)); +} + +void ASurfaceTransaction_setTransform(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, int32_t transform) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->setTransform(surfaceControl, transform); + bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) == + NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; + transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay); +} + void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, int8_t transparency) { diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index 3553c1f05310..80c68f29e4a5 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -26,6 +26,7 @@ package { java_library { name: "framework-connectivity-protos", sdk_version: "module_current", + min_sdk_version: "30", proto: { type: "nano", }, @@ -109,13 +110,15 @@ cc_defaults { "-Wall", "-Werror", "-Wno-unused-parameter", + // Don't warn about S API usage even with + // min_sdk 30: the library is only loaded + // on S+ devices + "-Wno-unguarded-availability", "-Wthread-safety", ], shared_libs: [ - "libbase", "liblog", "libnativehelper", - "libnetd_client", ], header_libs: [ "dnsproxyd_protocol_headers", @@ -137,12 +140,14 @@ cc_library_static { cc_library_shared { name: "libframework-connectivity-jni", + min_sdk_version: "30", defaults: ["libframework-connectivity-defaults"], srcs: [ + "jni/android_net_NetworkUtils.cpp", "jni/onload.cpp", ], shared_libs: ["libandroid"], - static_libs: ["libconnectivityframeworkutils"], + stl: "libc++_static", apex_available: [ "//apex_available:platform", "com.android.tethering", @@ -152,6 +157,7 @@ cc_library_shared { java_library { name: "framework-connectivity.impl", sdk_version: "module_current", + min_sdk_version: "30", srcs: [ ":framework-connectivity-sources", ], diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index f7c3965a7d24..2bf807c4b760 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -28,6 +28,14 @@ package android.net { field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION"; field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; + field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 + field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 + field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 + field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 + field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 + field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 + field public static final int BLOCKED_REASON_NONE = 0; // 0x0 + field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index b19efa3640c3..9dcc391bd31b 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -270,6 +270,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method @NonNull public int[] getAdministratorUids(); + method @Nullable public static String getCapabilityCarrierName(int); method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp index fd4d9db23d29..e8bb42df6acc 100644 --- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp +++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp @@ -37,7 +37,6 @@ #include <utils/Log.h> #include <utils/misc.h> -#include "NetdClient.h" #include "jni.h" extern "C" { @@ -113,14 +112,14 @@ static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobj } static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, - jint netId) + jint netId, jlong netHandle) { - return (jboolean) !setNetworkForResolv(netId); + return (jboolean) !android_setprocdns(netHandle); } -static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd, - jint netId) { - return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); +static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd, + jlong netHandle) { + return android_setsocknetwork(netHandle, AFileDescriptor_getFD(env, javaFd)); } static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) @@ -132,7 +131,7 @@ static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* return true; } -static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle, jstring dname, jint ns_class, jint ns_type, jint flags) { const jsize javaCharsCount = env->GetStringLength(dname); const jsize byteCountUTF8 = env->GetStringUTFLength(dname); @@ -142,7 +141,8 @@ static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint std::vector<char> queryname(byteCountUTF8 + 1, 0); env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); - int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags); if (fd < 0) { jniThrowErrnoException(env, "resNetworkQuery", -fd); @@ -152,12 +152,12 @@ static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint return jniCreateFileDescriptor(env, fd); } -static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle, jbyteArray msg, jint msgLen, jint flags) { uint8_t data[MAXCMDSIZE]; checkLenAndCopy(env, msg, msgLen, data); - int fd = resNetworkSend(netId, data, msgLen, flags); + int fd = android_res_nsend(netHandle, data, msgLen, flags); if (fd < 0) { jniThrowErrnoException(env, "resNetworkSend", -fd); @@ -172,7 +172,7 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job int rcode; std::vector<uint8_t> buf(MAXPACKETSIZE, 0); - int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE); jniSetFileDescriptorOfFD(env, javaFd, -1); if (res < 0) { jniThrowErrnoException(env, "resNetworkResult", -res); @@ -196,23 +196,22 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { int fd = AFileDescriptor_getFD(env, javaFd); - resNetworkCancel(fd); + android_res_cancel(fd); jniSetFileDescriptorOfFD(env, javaFd, -1); } static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { - unsigned dnsNetId = 0; - if (int res = getNetworkForDns(&dnsNetId) < 0) { - jniThrowErrnoException(env, "getDnsNetId", -res); + net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED; + if (int res = android_getprocdns(&dnsNetHandle) < 0) { + jniThrowErrnoException(env, "getDnsNetwork", -res); return nullptr; } - bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; static jclass class_Network = MakeGlobalRefOrDie( env, FindClassOrDie(env, "android/net/Network")); - static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V"); - return env->NewObject( - class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); + static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle", + "(J)Landroid/net/Network;"); + return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle)); } static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { @@ -261,12 +260,12 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle }, { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess }, { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, - { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork }, + { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle }, { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow }, - { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, - { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 20ff93f5d90a..a73d76e5b147 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -829,6 +829,94 @@ public class ConnectivityManager { }) public @interface PrivateDnsMode {} + /** + * Flag to indicate that an app is not subject to any restrictions that could result in its + * network access blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_NONE = 0; + + /** + * Flag to indicate that an app is subject to Battery saver restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0; + + /** + * Flag to indicate that an app is subject to Doze restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_DOZE = 1 << 1; + + /** + * Flag to indicate that an app is subject to App Standby restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2; + + /** + * Flag to indicate that an app is subject to Restricted mode restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; + + /** + * Flag to indicate that an app is subject to Data saver restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16; + + /** + * Flag to indicate that an app is subject to user restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17; + + /** + * Flag to indicate that an app is subject to Device admin restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BLOCKED_"}, value = { + BLOCKED_REASON_NONE, + BLOCKED_REASON_BATTERY_SAVER, + BLOCKED_REASON_DOZE, + BLOCKED_REASON_APP_STANDBY, + BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_METERED_REASON_DATA_SAVER, + BLOCKED_METERED_REASON_USER_RESTRICTED, + BLOCKED_METERED_REASON_ADMIN_DISABLED, + }) + public @interface BlockedReason {} + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; @@ -3234,7 +3322,60 @@ public class ConnectivityManager { provider.setProviderId(NetworkProvider.ID_NONE); } + /** + * Register or update a network offer with ConnectivityService. + * + * ConnectivityService keeps track of offers made by the various providers and matches + * them to networking requests made by apps or the system. The provider supplies a score + * and the capabilities of the network it might be able to bring up ; these act as filters + * used by ConnectivityService to only send those requests that can be fulfilled by the + * provider. + * + * The provider is under no obligation to be able to bring up the network it offers at any + * given time. Instead, this mechanism is meant to limit requests received by providers + * to those they actually have a chance to fulfill, as providers don't have a way to compare + * the quality of the network satisfying a given request to their own offer. + * + * An offer can be updated by calling this again with the same callback object. This is + * similar to calling unofferNetwork and offerNetwork again, but will only update the + * provider with the changes caused by the changes in the offer. + * + * @param provider The provider making this offer. + * @param score The prospective score of the network. + * @param caps The prospective capabilities of the network. + * @param callback The callback to call when this offer is needed or unneeded. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void offerNetwork(@NonNull final int providerId, + @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, + @NonNull final INetworkOfferCallback callback) { + try { + mService.offerNetwork(providerId, + Objects.requireNonNull(score, "null score"), + Objects.requireNonNull(caps, "null caps"), + Objects.requireNonNull(callback, "null callback")); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** + * Withdraw a network offer made with {@link #offerNetwork}. + * + * @param callback The callback passed at registration time. This must be the same object + * that was passed to {@link #offerNetwork} + * @hide + */ + public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { + try { + mService.unofferNetwork(Objects.requireNonNull(callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** @hide exposed via the NetworkProvider class. */ @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 0826922e2165..728f375372b1 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -23,6 +23,7 @@ import android.net.IConnectivityDiagnosticsCallback; import android.net.INetworkAgent; import android.net.IOnCompleteListener; import android.net.INetworkActivityListener; +import android.net.INetworkOfferCallback; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -221,4 +222,8 @@ interface IConnectivityManager in IOnCompleteListener listener); int getRestrictBackgroundStatusByCaller(); + + void offerNetwork(int providerId, in NetworkScore score, + in NetworkCapabilities caps, in INetworkOfferCallback callback); + void unofferNetwork(in INetworkOfferCallback callback); } diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl new file mode 100644 index 000000000000..67d2d405dbed --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.NetworkRequest; + +/** + * A callback registered with connectivity by network providers together with + * a NetworkOffer. + * + * When the offer is needed to satisfy some application or system component, + * connectivity will call onOfferNeeded on this callback. When this happens, + * the provider should try and bring up the network. + * + * When the offer is no longer needed, for example because the application has + * withdrawn the request or if the request is being satisfied by a network + * that this offer will never be able to beat, connectivity calls + * onOfferUnneeded. When this happens, the provider should stop trying to + * bring up the network, or tear it down if it has already been brought up. + * + * When NetworkProvider#offerNetwork is called, the provider can expect to + * immediately receive all requests that can be fulfilled by that offer and + * are not already satisfied by a better network. It is possible no such + * request is currently outstanding, because no requests have been made that + * can be satisfied by this offer, or because all such requests are already + * satisfied by a better network. + * onOfferNeeded can be called at any time after registration and until the + * offer is withdrawn with NetworkProvider#unofferNetwork is called. This + * typically happens when a new network request is filed by an application, + * or when the network satisfying a request disconnects and this offer now + * stands a chance to be the best network for it. + * + * @hide + */ +oneway interface INetworkOfferCallback { + /** + * Informs the registrant that the offer is needed to fulfill this request. + * @param networkRequest the request to satisfy + * @param providerId the ID of the provider currently satisfying + * this request, or NetworkProvider.ID_NONE if none. + */ + void onOfferNeeded(in NetworkRequest networkRequest, int providerId); + + /** + * Informs the registrant that the offer is no longer needed to fulfill this request. + */ + void onOfferUnneeded(in NetworkRequest networkRequest); +} diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java index 41fad6317a20..1f490337de93 100644 --- a/packages/Connectivity/framework/src/android/net/Network.java +++ b/packages/Connectivity/framework/src/android/net/Network.java @@ -92,6 +92,7 @@ public class Network implements Parcelable { // value in the native/android/net.c NDK implementation. private static final long HANDLE_MAGIC = 0xcafed00dL; private static final int HANDLE_MAGIC_SIZE = 32; + private static final int USE_LOCAL_NAMESERVERS_FLAG = 0x80000000; // A boolean to control how getAllByName()/getByName() behaves in the face // of Private DNS. @@ -189,7 +190,7 @@ public class Network implements Parcelable { */ public int getNetIdForResolv() { return mPrivateDnsBypass - ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag. + ? (USE_LOCAL_NAMESERVERS_FLAG | netId) // Non-portable DNS resolution flag. : netId; } @@ -452,12 +453,13 @@ public class Network implements Parcelable { throw new IllegalArgumentException( "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network."); } - if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC - || networkHandle < 0) { + if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC) { throw new IllegalArgumentException( "Value passed to fromNetworkHandle() is not a network handle."); } - return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE)); + final int netIdForResolv = (int) (networkHandle >>> HANDLE_MAGIC_SIZE); + return new Network((netIdForResolv & ~USE_LOCAL_NAMESERVERS_FLAG), + ((netIdForResolv & USE_LOCAL_NAMESERVERS_FLAG) != 0) /* privateDnsBypass */); } /** @@ -485,7 +487,7 @@ public class Network implements Parcelable { if (netId == 0) { return 0L; // make this zero condition obvious for debugging } - return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; + return (((long) getNetIdForResolv()) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; } // implement the Parcelable interface diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 27f7ee28130d..f50b0187b8ad 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -711,6 +711,23 @@ public final class NetworkCapabilities implements Parcelable { return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); } + /** + * Get the name of the given capability that carriers use. + * If the capability does not have a carrier-name, returns null. + * + * @param capability The capability to get the carrier-name of. + * @return The carrier-name of the capability, or null if it doesn't exist. + * @hide + */ + @SystemApi + public static @Nullable String getCapabilityCarrierName(@NetCapability int capability) { + if (capability == NET_CAPABILITY_ENTERPRISE) { + return capabilityNameOf(capability); + } else { + return null; + } + } + private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities; final long unwantedCaps = diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java index 14cb51c85d06..d5b5c9b603ec 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java +++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java @@ -28,6 +28,11 @@ import android.os.Message; import android.os.Messenger; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + /** * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device * to networks and makes them available to the core network stack by creating @@ -78,7 +83,9 @@ public class NetworkProvider { */ @SystemApi public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { - Handler handler = new Handler(looper) { + // TODO (b/174636568) : this class should be able to cache an instance of + // ConnectivityManager so it doesn't have to fetch it again every time. + final Handler handler = new Handler(looper) { @Override public void handleMessage(Message m) { switch (m.what) { @@ -159,4 +166,152 @@ public class NetworkProvider { public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request); } + + /** @hide */ + // TODO : make @SystemApi when the impl is complete + public interface NetworkOfferCallback { + /** Called by the system when this offer is needed to satisfy some networking request. */ + void onOfferNeeded(@NonNull NetworkRequest request, int providerId); + /** Called by the system when this offer is no longer needed. */ + void onOfferUnneeded(@NonNull NetworkRequest request); + } + + private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub { + @NonNull public final NetworkOfferCallback callback; + @NonNull private final Executor mExecutor; + + NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback, + @NonNull final Executor executor) { + this.callback = callback; + this.mExecutor = executor; + } + + @Override + public void onOfferNeeded(final @NonNull NetworkRequest request, + final int providerId) { + mExecutor.execute(() -> callback.onOfferNeeded(request, providerId)); + } + + @Override + public void onOfferUnneeded(final @NonNull NetworkRequest request) { + mExecutor.execute(() -> callback.onOfferUnneeded(request)); + } + } + + @GuardedBy("mProxies") + @NonNull private final ArrayList<NetworkOfferCallbackProxy> mProxies = new ArrayList<>(); + + // Returns the proxy associated with this callback, or null if none. + @Nullable + private NetworkOfferCallbackProxy findProxyForCallback(@NonNull final NetworkOfferCallback cb) { + synchronized (mProxies) { + for (final NetworkOfferCallbackProxy p : mProxies) { + if (p.callback == cb) return p; + } + } + return null; + } + + /** + * Register or update an offer for network with the passed caps and score. + * + * A NetworkProvider's job is to provide networks. This function is how a provider tells the + * connectivity stack what kind of network it may provide. The score and caps arguments act + * as filters that the connectivity stack uses to tell when the offer is necessary. When an + * offer might be advantageous over existing networks, the provider will receive a call to + * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider + * should then try to bring up this network. When an offer is no longer needed, the stack + * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The + * provider should stop trying to bring up such a network, or disconnect it if it already has + * one. + * + * The stack determines what offers are needed according to what networks are currently + * available to the system, and what networking requests are made by applications. If an + * offer looks like it could be a better choice than any existing network for any particular + * request, that's when the stack decides the offer is needed. If the current networking + * requests are all satisfied by networks that this offer can't possibly be a better match + * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider + * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded} + * is called. + * + * Note that the offers are non-binding to the providers, in particular because providers + * often don't know if they will be able to bring up such a network at any given time. For + * example, no wireless network may be in range when the offer is needed. This is fine and + * expected ; the provider should simply continue to try to bring up the network and do so + * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests + * with the best network currently available, or if none, keep the apps informed that no + * network can currently satisfy this request. When/if the provider can bring up the network, + * the connectivity stack will match it against requests, and inform interested apps of the + * availability of this network. This may, in turn, render the offer of some other provider + * unneeded if all requests it used to satisfy are now better served by this network. + * + * A network can become unneeded for a reason like the above : whether the provider managed + * to bring up the offered network after it became needed or not, some other provider may + * bring up a better network than this one, making this offer unneeded. A network may also + * become unneeded if the application making the request withdrew it (for example, after it + * is done transferring data, or if the user canceled an operation). + * + * The capabilities and score act as filters as to what requests the provider will see. + * They are not promises, but for best performance, the providers should strive to put + * as much known information as possible in the offer. For capabilities in particular, it + * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't + * have them at first. This applies to INTERNET, for example ; if a provider thinks the + * network it can bring up for this offer may offer Internet access it should include the + * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET. + * + * TODO : in the future, to avoid possible infinite loops, there should be constraints on + * what can be put in capabilities of networks brought up for an offer. If a provider might + * bring up a network with or without INTERNET, then it should file two offers : this will + * let it know precisely what networks are needed, so it can avoid bringing up networks that + * won't actually satisfy requests and remove the risk for bring-up-bring-down loops. + * + * @hide + */ + // TODO : make @SystemApi when the impl is complete + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void offerNetwork(@NonNull final NetworkScore score, + @NonNull final NetworkCapabilities caps, @NonNull final Executor executor, + @NonNull final NetworkOfferCallback callback) { + // Can't offer a network with a provider that is not yet registered or already unregistered. + final int providerId = mProviderId; + if (providerId == ID_NONE) return; + NetworkOfferCallbackProxy proxy = null; + synchronized (mProxies) { + for (final NetworkOfferCallbackProxy existingProxy : mProxies) { + if (existingProxy.callback == callback) { + proxy = existingProxy; + break; + } + } + if (null == proxy) { + proxy = new NetworkOfferCallbackProxy(callback, executor); + mProxies.add(proxy); + } + } + mContext.getSystemService(ConnectivityManager.class) + .offerNetwork(providerId, score, caps, proxy); + } + + /** + * Withdraw a network offer previously made to the networking stack. + * + * If a provider can no longer provide a network they offered, it should call this method. + * An example of usage could be if the hardware necessary to bring up the network was turned + * off in UI by the user. Note that because offers are never binding, the provider might + * alternatively decide not to withdraw this offer and simply refuse to bring up the network + * even when it's needed. However, withdrawing the request is slightly more resource-efficient + * because the networking stack won't have to compare this offer to exiting networks to see + * if it could beat any of them, and may be advantageous to the provider's implementation that + * can rely on no longer receiving callbacks for a network that they can't bring up anyways. + * + * @hide + */ + // TODO : make @SystemApi when the impl is complete + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void unofferNetwork(final @NonNull NetworkOfferCallback callback) { + final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback); + if (null == proxy) return; + mProxies.remove(proxy); + mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy); + } } diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java index f52485933c49..16a49bcae7ee 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java +++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java @@ -92,12 +92,16 @@ public class NetworkUtils { @Deprecated public native static boolean bindProcessToNetworkForHostResolution(int netId); + private static native int bindSocketToNetworkHandle(FileDescriptor fd, long netHandle); + /** * Explicitly binds {@code fd} to the network designated by {@code netId}. This * overrides any binding via {@link #bindProcessToNetwork}. * @return 0 on success or negative errno on failure. */ - public static native int bindSocketToNetwork(FileDescriptor fd, int netId); + public static int bindSocketToNetwork(FileDescriptor fd, int netId) { + return bindSocketToNetworkHandle(fd, new Network(netId).getNetworkHandle()); + } /** * Determine if {@code uid} can access network designated by {@code netId}. @@ -108,14 +112,22 @@ public class NetworkUtils { return false; } + private static native FileDescriptor resNetworkSend( + long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException; + /** * DNS resolver series jni method. * Issue the query {@code msg} on the network designated by {@code netId}. * {@code flags} is an additional config to control actual querying behavior. * @return a file descriptor to watch for read events */ - public static native FileDescriptor resNetworkSend( - int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + public static FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException { + return resNetworkSend(new Network(netId).getNetworkHandle(), msg, msglen, flags); + } + + private static native FileDescriptor resNetworkQuery( + long netHandle, String dname, int nsClass, int nsType, int flags) throws ErrnoException; /** * DNS resolver series jni method. @@ -124,8 +136,11 @@ public class NetworkUtils { * {@code flags} is an additional config to control actual querying behavior. * @return a file descriptor to watch for read events */ - public static native FileDescriptor resNetworkQuery( - int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + public static FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException { + return resNetworkQuery(new Network(netId).getNetworkHandle(), dname, nsClass, nsType, + flags); + } /** * DNS resolver series jni method. diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index 37dd9ff84b59..9d1bb0fc438f 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -25,7 +25,7 @@ package { cc_library_shared { name: "libservice-connectivity", - // TODO: build against the NDK (sdk_version: "30" for example) + min_sdk_version: "30", cflags: [ "-Wall", "-Werror", @@ -36,13 +36,13 @@ cc_library_shared { "jni/com_android_server_TestNetworkService.cpp", "jni/onload.cpp", ], + stl: "libc++_static", + header_libs: [ + "libbase_headers", + ], shared_libs: [ - "libbase", "liblog", "libnativehelper", - // TODO: remove dependency on ifc_[add/del]_address by having Java code to add/delete - // addresses, and remove dependency on libnetutils. - "libnetutils", ], apex_available: [ "com.android.tethering", @@ -52,6 +52,7 @@ cc_library_shared { java_library { name: "service-connectivity-pre-jarjar", sdk_version: "system_server_current", + min_sdk_version: "30", srcs: [ ":connectivity-service-srcs", ":framework-connectivity-shared-srcs", @@ -90,6 +91,7 @@ java_library { java_library { name: "service-connectivity-protos", sdk_version: "system_current", + min_sdk_version: "30", proto: { type: "nano", }, @@ -106,6 +108,7 @@ java_library { java_library { name: "service-connectivity", sdk_version: "system_server_current", + min_sdk_version: "30", installable: true, static_libs: [ "service-connectivity-pre-jarjar", diff --git a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp index fa4501ac7f29..d78373811194 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp +++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp @@ -22,6 +22,7 @@ package { android_app { name: "ServiceConnectivityResources", sdk_version: "module_current", + min_sdk_version: "30", resource_dirs: [ "res", ], diff --git a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp index 36a6fde36191..e7a40e5ea66b 100644 --- a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp +++ b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp @@ -35,8 +35,6 @@ #include <log/log.h> -#include "netutils/ifc.h" - #include "jni.h" #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> @@ -48,9 +46,8 @@ namespace android { //------------------------------------------------------------------------------ static void throwException(JNIEnv* env, int error, const char* action, const char* iface) { - const std::string& msg = - android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error)); - + const std::string& msg = "Error: " + std::string(action) + " " + std::string(iface) + ": " + + std::string(strerror(error)); jniThrowException(env, "java/lang/IllegalStateException", msg.c_str()); } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index d6c66b5663af..5e69a4ee395c 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -17,8 +17,8 @@ android_library { // TODO(b/149540986): revert this change. static_libs: [ - // All other dependent components should be put in - // "SettingsLibDependenciesWithoutWifiTracker". + // All other dependent components should be put in + // "SettingsLibDependenciesWithoutWifiTracker". "WifiTrackerLib", ], @@ -27,7 +27,10 @@ android_library { resource_dirs: ["res"], - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], min_sdk_version: "21", @@ -68,6 +71,7 @@ java_defaults { "SettingsLibUsageProgressBarPreference", "SettingsLibCollapsingToolbarBaseActivity", "SettingsLibTwoTargetPreference", + "SettingsLibSettingsTransition", ], } diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml index 8c208e35a371..0390e86a0cf6 100644 --- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml +++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml @@ -21,7 +21,7 @@ android:background="?android:attr/selectableItemBackground" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingStart="@dimen/app_preference_padding_start" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> <LinearLayout @@ -29,7 +29,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start|center_vertical" - android:minWidth="56dp" + android:minWidth="@dimen/app_icon_min_width" android:orientation="horizontal" android:paddingEnd="8dp" android:paddingTop="4dp" diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml index e1a64d446ea2..1157a346d6ec 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml @@ -17,10 +17,10 @@ <resources> <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> </style> - <style name="CollapsingToolbarTitle" - parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <style name="CollapsingToolbarTitle" parent="CollapsingToolbarTitle.Collapsed"> <item name="android:textSize">36sp</item> </style> diff --git a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml index 5496a0178094..7a550aed94ac 100644 --- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml +++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml @@ -23,6 +23,7 @@ android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:background="?android:attr/selectableItemBackground" + android:orientation="vertical" android:clipToPadding="false"> <LinearLayout diff --git a/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml new file mode 100644 index 000000000000..55677908b684 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/icon_min_width" + android:gravity="start|center_vertical" + android:orientation="horizontal" + android:paddingLeft="0dp" + android:paddingStart="0dp" + android:paddingRight="8dp" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> + + <androidx.preference.internal.PreferenceImageView + android:id="@android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:maxWidth="48dp" + app:maxHeight="48dp"/> + +</LinearLayout> diff --git a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml deleted file mode 100644 index 4a1b089970c7..000000000000 --- a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingRight="?android:attr/listPreferredItemPaddingRight" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:background="?android:attr/selectableItemBackground" - android:baselineAligned="false" - android:layout_marginTop="16dp" - android:gravity="center_vertical" - style="@style/PreferenceCategoryStartMargin"> - - <RelativeLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:paddingTop="8dp" - android:paddingBottom="8dp"> - - <TextView - android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textAlignment="viewStart" - style="@style/PreferenceCategoryTitleTextStyle"/> - - <TextView - android:id="@android:id/summary" - android:ellipsize="end" - android:singleLine="true" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@android:id/title" - android:layout_alignLeft="@android:id/title" - android:layout_alignStart="@android:id/title" - android:layout_gravity="start" - android:textAlignment="viewStart" - android:textColor="?android:attr/textColorSecondary" - android:maxLines="10" - style="@style/PreferenceSummaryTextStyle"/> - </RelativeLayout> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml new file mode 100644 index 000000000000..87977bdcd6b3 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<!-- We use a FrameLayout as we want to place the invisible spinner on top of the other views --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <!-- This spinner should be invisible in the layout and take up no space, when the Preference + is clicked the dropdown will appear from this location on screen. --> + <Spinner + android:id="@+id/spinner" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/preference_dropdown_padding_start" + android:layout_marginLeft="@dimen/preference_dropdown_padding_start" + android:visibility="invisible" /> + + <include layout="@layout/settings_preference" /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml new file mode 100644 index 000000000000..3a289a7ceb00 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:gravity="center_vertical" + android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingRight="?android:attr/listPreferredItemPaddingRight" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:background="?android:attr/selectableItemBackground" + android:clipToPadding="false" + android:baselineAligned="false"> + + <include layout="@layout/image_frame"/> + + <RelativeLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="16dp" + android:paddingBottom="16dp"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceListItem" + android:ellipsize="marquee"/> + + <TextView + android:id="@android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignLeft="@android:id/title" + android:layout_alignStart="@android:id/title" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="10" + style="@style/PreferenceSummaryTextStyle"/> + + </RelativeLayout> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="end|center_vertical" + android:paddingLeft="16dp" + android:paddingStart="16dp" + android:paddingRight="0dp" + android:paddingEnd="0dp" + android:orientation="vertical"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml new file mode 100644 index 000000000000..acf06c4ffc29 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <bool name="config_icon_space_reserved">false</bool> + <bool name="config_allow_divider">false</bool> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml new file mode 100644 index 000000000000..17b68050a3cc --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <dimen name="preference_title_font_size">20sp</dimen> + <dimen name="icon_min_width">48dp</dimen> + <dimen name="preference_padding_start">24dp</dimen> + <dimen name="preference_padding_end">24dp</dimen> + <dimen name="app_preference_padding_start">20dp</dimen> + <dimen name="app_icon_min_width">52dp</dimen> +</resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml new file mode 100644 index 000000000000..a3bb1da71c9a --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <bool name="config_icon_space_reserved">true</bool> + <bool name="config_allow_divider">true</bool> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml index 94856552d6e2..009ae6bc46a9 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml @@ -17,4 +17,10 @@ <resources> <dimen name="secondary_app_icon_size">32dp</dimen> + <dimen name="preference_title_font_size">16sp</dimen> + <dimen name="icon_min_width">56dp</dimen> + <dimen name="preference_padding_start">?android:attr/dialogPreferredPadding</dimen> + <dimen name="preference_padding_end">?android:attr/dialogPreferredPadding</dimen> + <dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen> + <dimen name="app_icon_min_width">56dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml index a6623b01fede..f24e0083db27 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml @@ -15,16 +15,8 @@ limitations under the License. --> <resources> - <style name="TextAppearance.CategoryTitle" - parent="@*android:style/TextAppearance.DeviceDefault.Body2"> - <item name="android:textAllCaps">true</item> - <item name="android:textSize">11sp</item> - <!-- 0.8 Spacing, 0.8/11 = 0.072727273 --> - <item name="android:letterSpacing">0.072727273</item> - </style> - - <style name="PreferenceCategoryStartMargin"> - <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingLeft</item> - <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item> + <style name="TextAppearance.PreferenceTitle" + parent="@*android:style/TextAppearance.DeviceDefault.ListItem"> + <item name="android:textSize">@dimen/preference_title_font_size</item> </style> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml index 6ca8f5775f42..17596ac2903a 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml @@ -16,17 +16,54 @@ --> <resources> <style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay"> - <item name="footerPreferenceStyle">@style/Preference.Material</item> <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item> - <!-- For preference category color --> - <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle - </item> - <item name="preferenceCategoryTitleTextColor">?android:attr/textColorSecondary</item> + <item name="preferenceStyle">@style/SettingsPreference</item> + <item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference</item> + <item name="dialogPreferenceStyle">@style/SettingsPreference</item> + <item name="editTextPreferenceStyle">@style/SettingsEditTextPreference</item> + <item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference</item> + <item name="switchPreferenceStyle">@style/SettingsSwitchPreference</item> + <item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference</item> + <item name="footerPreferenceStyle">@style/Preference.Material</item> </style> <style name="SettingsCategoryPreference" parent="@style/Preference.Category.Material"> - <item name="android:layout">@layout/preference_category_settings</item> - <item name="allowDividerAbove">false</item> - <item name="allowDividerBelow">false</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + <item name="allowDividerAbove">@bool/config_allow_divider</item> + <item name="allowDividerBelow">@bool/config_allow_divider</item> + </style> + + <style name="SettingsPreference" parent="@style/Preference.Material"> + <item name="layout">@layout/settings_preference</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingsCheckBoxPreference" parent="@style/Preference.CheckBoxPreference.Material"> + <item name="layout">@layout/settings_preference</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingsEditTextPreference" + parent="@style/Preference.DialogPreference.EditTextPreference.Material"> + <item name="layout">@layout/settings_preference</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingsDropdownPreference" parent="@style/Preference.DropDown.Material"> + <item name="layout">@layout/settings_dropdown_preference</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingsSwitchPreference" parent="@style/Preference.SwitchPreference.Material"> + <item name="layout">@layout/settings_preference</item> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingsSeekbarPreference" parent="@style/Preference.SeekBarPreference.Material"> + <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item> + </style> + + <style name="SettingFooterPreference" parent="@style/Preference.Material"> + <item name="allowDividerAbove">@bool/config_allow_divider</item> </style> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml new file mode 100644 index 000000000000..36ca684a14a3 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <!-- Only using in Settings application --> + <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" > + <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle</item> + <item name="android:listPreferredItemPaddingStart">@dimen/preference_padding_start</item> + <item name="android:listPreferredItemPaddingEnd">@dimen/preference_padding_end</item> + <item name="preferenceTheme">@style/PreferenceTheme</item> + </style> + + <!-- Using in SubSettings page including injected settings page --> + <style name="Theme.SubSettingsBase" parent="Theme.SettingsBase"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp new file mode 100644 index 000000000000..c12f6f748cba --- /dev/null +++ b/packages/SettingsLib/SettingsTransition/Android.bp @@ -0,0 +1,22 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "SettingsLibSettingsTransition", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "com.google.android.material_material", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml new file mode 100644 index 000000000000..11660a5fe444 --- /dev/null +++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.transition"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml index 4f402565787c..0630ca85b621 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml +++ b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml @@ -15,8 +15,5 @@ --> <resources> - <style name="PreferenceCategoryStartMargin"> - <item name="android:paddingLeft">24dp</item> - <item name="android:paddingStart">24dp</item> - </style> -</resources> + <dimen name="settings_shared_axis_x_slide_distance">96dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java new file mode 100644 index 000000000000..f99fda05c3c9 --- /dev/null +++ b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.transition; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.view.Window; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import com.google.android.material.transition.platform.MaterialSharedAxis; +import com.google.android.material.transition.platform.SlideDistanceProvider; + +/** + * A helper class to apply Settings Transition + */ +public class SettingsTransitionHelper { + + private static final String TAG = "SettingsTransitionHelper"; + private static final long DURATION = 450L; + + private static MaterialSharedAxis createSettingsSharedAxis(Context context, boolean forward) { + final MaterialSharedAxis transition = new MaterialSharedAxis(MaterialSharedAxis.X, forward); + transition.excludeTarget(android.R.id.statusBarBackground, true); + transition.excludeTarget(android.R.id.navigationBarBackground, true); + + final SlideDistanceProvider forwardDistanceProvider = + (SlideDistanceProvider) transition.getPrimaryAnimatorProvider(); + final int distance = context.getResources().getDimensionPixelSize( + R.dimen.settings_shared_axis_x_slide_distance); + forwardDistanceProvider.setSlideDistance(distance); + transition.setDuration(DURATION); + + final Interpolator interpolator = + AnimationUtils.loadInterpolator(context, + android.R.interpolator.fast_out_extra_slow_in); + transition.setInterpolator(interpolator); + + // TODO(b/177480673): Update fade through threshold once (cl/362065364) is released + + return transition; + } + + /** + * Apply the forward transition to the {@link Activity}, including Exit Transition and Enter + * Transition. + * + * The Exit Transition takes effect when leaving the page, while the Enter Transition is + * triggered when the page is launched/entering. + */ + public static void applyForwardTransition(Activity activity) { + if (activity == null) { + Log.w(TAG, "applyForwardTransition: Invalid activity!"); + return; + } + final Window window = activity.getWindow(); + if (window == null) { + Log.w(TAG, "applyForwardTransition: Invalid window!"); + return; + } + final MaterialSharedAxis forward = createSettingsSharedAxis(activity, true); + window.setExitTransition(forward); + window.setEnterTransition(forward); + } + + /** + * Apply the backward transition to the {@link Activity}, including Return Transition and + * Reenter Transition. + * + * Return Transition will be used to move Views out of the scene when the Window is preparing + * to close. Reenter Transition will be used to move Views in to the scene when returning from a + * previously-started Activity. + */ + public static void applyBackwardTransition(Activity activity) { + if (activity == null) { + Log.w(TAG, "applyBackwardTransition: Invalid activity!"); + return; + } + final Window window = activity.getWindow(); + if (window == null) { + Log.w(TAG, "applyBackwardTransition: Invalid window!"); + return; + } + final MaterialSharedAxis backward = createSettingsSharedAxis(activity, false); + window.setReturnTransition(backward); + window.setReenterTransition(backward); + } +} diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java index 9130662021d5..17f257d3758a 100644 --- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java +++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java @@ -72,9 +72,9 @@ public class TwoTargetPreference extends Preference { private void init(Context context) { setLayoutResource(R.layout.preference_two_target); mSmallIconSize = context.getResources().getDimensionPixelSize( - R.dimen.two_target_pref_small_icon_size); + resourceId(context, "dimen", "two_target_pref_small_icon_size")); mMediumIconSize = context.getResources().getDimensionPixelSize( - R.dimen.two_target_pref_medium_icon_size); + resourceId(context, "dimen", "two_target_pref_medium_icon_size")); final int secondTargetResId = getSecondTargetResId(); if (secondTargetResId != 0) { setWidgetLayoutResource(secondTargetResId); @@ -116,4 +116,8 @@ public class TwoTargetPreference extends Preference { protected int getSecondTargetResId() { return 0; } + + private int resourceId(Context context, String type, String name) { + return context.getResources().getIdentifier(name, type, context.getPackageName()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java index dfde3c7a2512..c61f8a9c3b53 100644 --- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java @@ -250,6 +250,7 @@ public class ConnectivitySubsystemsRecoveryManager { * @param callback Callbacks triggered when recovery status changes. */ public void triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback) { + // TODO: b/183530649 : clean-up or make use of the `reason` argument mHandler.post(() -> { boolean someSubsystemRestarted = false; @@ -264,7 +265,7 @@ public class ConnectivitySubsystemsRecoveryManager { } if (isWifiEnabled()) { - mWifiManager.restartWifiSubsystem(reason); + mWifiManager.restartWifiSubsystem(); mWifiRestartInProgress = true; someSubsystemRestarted = true; startTrackingWifiRestart(); diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java index 1474f184775d..f62ca3294665 100644 --- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java @@ -86,6 +86,15 @@ public abstract class AbstractEnableAdbPreferenceController extends } } + @Override + protected void onDeveloperOptionsSwitchEnabled() { + super.onDeveloperOptionsSwitchEnabled(); + if (isAvailable()) { + mPreference.setDisabledByAdmin( + checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId())); + } + } + public void enablePreference(boolean enabled) { if (isAvailable()) { mPreference.setEnabled(enabled); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 068efac5ce71..3877b1e00048 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -749,7 +749,8 @@ public class SettingsBackupTest { Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER, Settings.Secure.SUPPRESS_DOZE, Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, - Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT); + Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, + Settings.Secure.TRANSFORM_ENABLED); @Test public void systemSettingsBackedUpOrDenied() { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java index beee03b52579..caa9b4f4ec18 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java @@ -16,6 +16,7 @@ package com.android.systemui.plugins.qs; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.view.View; import android.view.ViewGroup; @@ -34,7 +35,22 @@ public interface DetailAdapter { } View createDetailView(Context context, View convertView, ViewGroup parent); + + /** + * @return intent for opening more settings related to this detail panel. If null, the more + * settings button will not be shown + */ Intent getSettingsIntent(); + + /** + * @return resource id of the string to use for opening the settings intent. If + * {@code Resources.ID_NULL}, then use the default string: + * {@code com.android.systemui.R.string.quick_settings_more_settings} + */ + default int getSettingsText() { + return Resources.ID_NULL; + } + void setToggleState(boolean state); int getMetricsCategory(); diff --git a/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml new file mode 100644 index 000000000000..e49fc15fbd7f --- /dev/null +++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack"> + <item android:id="@android:id/background" + android:gravity="center_vertical|fill_horizontal"> + <layer-list> + <item android:id="@+id/volume_seekbar_background_solid"> + <shape> + <size android:height="@dimen/volume_dialog_slider_width" /> + <solid android:color="@color/tv_volume_dialog_seek_bar_background"/> + <corners android:radius="@dimen/volume_dialog_slider_corner_radius" /> + </shape> + </item> + </layer-list> + </item> + <item android:id="@android:id/progress" + android:gravity="center_vertical|fill_horizontal"> + <com.android.systemui.util.RoundedCornerProgressDrawable + android:drawable="@drawable/volume_row_seekbar_progress" + /> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml new file mode 100644 index 000000000000..bce193a564a0 --- /dev/null +++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up + and down as the progress value changes. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true"> + <item android:id="@+id/volume_seekbar_progress_solid"> + <shape android:shape="rectangle"> + <size android:height="@dimen/volume_dialog_slider_width"/> + <solid android:color="@color/tv_volume_dialog_seek_bar_fill" /> + <corners android:radius="@dimen/volume_dialog_slider_width" /> + </shape> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml deleted file mode 100644 index fe76b639f15a..000000000000 --- a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@android:id/background"> - <shape android:shape="rectangle"> - <solid android:color="@color/tv_volume_dialog_seek_bar_background" /> - <corners android:radius="@dimen/tv_volume_seek_bar_width" /> - </shape> - </item> - <item android:id="@android:id/progress"> - <clip> - <shape android:shape="rectangle"> - <solid android:color="@color/tv_volume_dialog_seek_bar_fill" /> - <corners android:radius="@dimen/tv_volume_seek_bar_width" /> - </shape> - </clip> - </item> -</layer-list> diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml index b0e0ed5079e6..a845e73eb3a4 100644 --- a/packages/SystemUI/res/drawable/volume_row_seekbar.xml +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -25,9 +25,9 @@ <layer-list> <item android:id="@+id/volume_seekbar_background_solid"> <shape> - <size android:height="@dimen/volume_dialog_panel_width" /> + <size android:height="@dimen/volume_dialog_slider_width" /> <solid android:color="?android:attr/colorBackgroundFloating" /> - <corners android:radius="@dimen/volume_dialog_panel_width_half" /> + <corners android:radius="@dimen/volume_dialog_slider_corner_radius" /> </shape> </item> <item @@ -53,4 +53,4 @@ android:drawable="@drawable/volume_row_seekbar_progress" /> </item> -</layer-list>
\ No newline at end of file +</layer-list> diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml index 4f6cb0140d09..0dec981bd4ae 100644 --- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml @@ -17,7 +17,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:tag="row" android:layout_width="wrap_content" - android:layout_height="@dimen/volume_dialog_row_height" + android:layout_height="@dimen/volume_dialog_panel_height" android:background="@android:color/transparent" android:clipChildren="false" android:clipToPadding="false" @@ -54,18 +54,18 @@ <FrameLayout android:id="@+id/volume_row_slider_frame" android:layout_width="match_parent" - android:layout_height="@dimen/volume_dialog_row_height"> + android:layout_height="@dimen/volume_dialog_panel_height"> <SeekBar android:id="@+id/volume_row_slider" android:clickable="false" - android:layout_width="@dimen/volume_dialog_row_height" + android:layout_width="@dimen/volume_dialog_panel_height" android:layout_height="match_parent" android:layout_gravity="center" android:layoutDirection="ltr" - android:maxHeight="@dimen/tv_volume_seek_bar_width" - android:minHeight="@dimen/tv_volume_seek_bar_width" + android:maxHeight="@dimen/volume_dialog_slider_width" + android:minHeight="@dimen/volume_dialog_slider_width" + android:progressDrawable="@drawable/volume_row_seekbar" android:thumb="@drawable/tv_volume_row_seek_thumb" - android:progressDrawable="@drawable/tv_volume_row_seek_bar" android:splitTrack="false" android:rotation="270" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml index f2341b55488f..9990244ba6c5 100644 --- a/packages/SystemUI/res/layout/people_tile_large_with_content.xml +++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml @@ -22,27 +22,50 @@ android:padding="16dp" android:orientation="vertical"> - <LinearLayout - android:layout_width="wrap_content" + <RelativeLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="start|top" - android:orientation="horizontal"> + android:gravity="start|top"> - <ImageView - android:id="@+id/person_icon" - android:layout_marginStart="-2dp" - android:layout_marginTop="-2dp" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" /> + android:layout_alignParentStart="true" + android:gravity="start|top" + android:orientation="horizontal"> - <ImageView - android:id="@+id/availability" - android:layout_marginStart="-2dp" - android:layout_width="10dp" - android:layout_height="10dp" - android:background="@drawable/circle_green_10dp" /> - </LinearLayout> + <ImageView + android:id="@+id/person_icon" + android:layout_marginStart="-2dp" + android:layout_marginTop="-2dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <ImageView + android:id="@+id/availability" + android:layout_marginStart="-2dp" + android:layout_width="10dp" + android:layout_height="10dp" + android:background="@drawable/circle_green_10dp" /> + </LinearLayout> + + <TextView + android:id="@+id/messages_count" + android:layout_alignParentEnd="true" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:background="@drawable/people_space_messages_count_background" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + /> + </RelativeLayout> <TextView android:layout_gravity="center" diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml index 914ee3c089d4..f0ab187b1a05 100644 --- a/packages/SystemUI/res/layout/people_tile_small.xml +++ b/packages/SystemUI/res/layout/people_tile_small.xml @@ -33,20 +33,36 @@ android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" /> + android:layout_weight="1" + android:paddingBottom="4dp"/> <ImageView android:id="@+id/predefined_icon" android:layout_gravity="center" - android:paddingTop="4dp" android:layout_width="18dp" android:layout_height="22dp" android:layout_weight="1" /> <TextView + android:id="@+id/messages_count" + android:layout_gravity="center" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:background="@drawable/people_space_messages_count_background" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_weight="1" + /> + + <TextView android:id="@+id/name" android:layout_gravity="center" - android:paddingTop="4dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index fda59b50104a..1d5bca92ddb0 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -54,6 +54,7 @@ android:layout_width="@dimen/volume_row_slider_height" android:layout_height="match_parent" android:layout_gravity="center" + android:thumb="@android:color/transparent" android:rotation="270" /> </FrameLayout> diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml index 95ff59bdf1df..8c905739b991 100644 --- a/packages/SystemUI/res/values-land-television/dimens.xml +++ b/packages/SystemUI/res/values-land-television/dimens.xml @@ -16,14 +16,17 @@ <resources> <!-- Height of volume bar --> - <dimen name="volume_dialog_row_height">190dp</dimen> - <dimen name="volume_dialog_row_width">48dp</dimen> + <dimen name="volume_dialog_panel_height">190dp</dimen> + <dimen name="volume_dialog_panel_width">48dp</dimen> + <dimen name="volume_dialog_panel_width_half">24dp</dimen> <dimen name="volume_dialog_panel_transparent_padding">24dp</dimen> + <dimen name="volume_dialog_slider_width">4dp</dimen> + <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_slider_width</dimen> + <dimen name="tv_volume_dialog_bubble_size">36dp</dimen> <dimen name="tv_volume_dialog_corner_radius">36dp</dimen> <dimen name="tv_volume_dialog_row_padding">6dp</dimen> <dimen name="tv_volume_number_text_size">16sp</dimen> - <dimen name="tv_volume_seek_bar_width">4dp</dimen> <dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen> <dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen> <dimen name="tv_volume_icons_size">20dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index af6df32a02b0..fbe7175b757f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -591,4 +591,12 @@ <!-- Determines whether to allow the nav bar handle to be forced to be opaque. --> <bool name="allow_force_nav_bar_handle_opaque">true</bool> + + <!-- Whether a transition of ACTIVITY_TYPE_DREAM to the home app should play a home sound + effect --> + <bool name="config_playHomeSoundAfterDream">false</bool> + + <!-- Whether a transition of ACTIVITY_TYPE_ASSISTANT to the home app should play a home sound + effect --> + <bool name="config_playHomeSoundAfterAssistant">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2393b749b64e..d5c6398dd719 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -462,6 +462,10 @@ <dimen name="volume_dialog_slider_height">116dp</dimen> + <dimen name="volume_dialog_slider_width">@dimen/volume_dialog_panel_width</dimen> + + <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_panel_width_half</dimen> + <dimen name="volume_dialog_ringer_size">64dp</dimen> <dimen name="volume_dialog_ringer_icon_padding">20dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 94bf86ab07da..935f0259fe86 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -889,6 +889,8 @@ <string name="quick_settings_color_space_label">Color correction mode</string> <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_settings">More settings</string> + <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] --> + <string name="quick_settings_more_user_settings">User settings</string> <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] --> <string name="quick_settings_done">Done</string> <!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 72dd72eb1676..02a8958ef657 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -487,5 +487,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> mView.setLayoutParams(lp); } } + + if (mKeyguardSecurityContainerController != null) { + mKeyguardSecurityContainerController.updateResources(); + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 4887767b9922..708b2d55b75a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -33,7 +33,6 @@ import android.util.MathUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; -import android.view.OrientationEventListener; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -107,7 +106,6 @@ public class KeyguardSecurityContainer extends FrameLayout { private boolean mOneHandedMode = false; private SecurityMode mSecurityMode = SecurityMode.Invalid; private ViewPropertyAnimator mRunningOneHandedAnimator; - private final OrientationEventListener mOrientationEventListener; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -247,13 +245,6 @@ public class KeyguardSecurityContainer extends FrameLayout { super(context, attrs, defStyle); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); mViewConfiguration = ViewConfiguration.get(context); - - mOrientationEventListener = new OrientationEventListener(context) { - @Override - public void onOrientationChanged(int orientation) { - updateLayoutForSecurityMode(mSecurityMode); - } - }; } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { @@ -262,7 +253,6 @@ public class KeyguardSecurityContainer extends FrameLayout { updateBiometricRetry(securityMode, faceAuthEnabled); updateLayoutForSecurityMode(securityMode); - mOrientationEventListener.enable(); } void updateLayoutForSecurityMode(SecurityMode securityMode) { @@ -385,7 +375,6 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); - mOrientationEventListener.disable(); } @Override @@ -663,6 +652,15 @@ public class KeyguardSecurityContainer extends FrameLayout { childState << MEASURED_HEIGHT_STATE_SHIFT)); } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + // After a layout pass, we need to re-place the inner bouncer, as our bounds may have + // changed. + updateSecurityViewLocation(/* animate= */false); + } + void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index ccba1d59c8d0..760eaecae247 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -29,6 +29,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.metrics.LogMaker; import android.os.UserHandle; import android.util.Log; @@ -74,6 +75,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final SecurityCallback mSecurityCallback; private final ConfigurationController mConfigurationController; + private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; + private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { @@ -212,6 +215,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( mKeyguardSecurityCallback); mConfigurationController = configurationController; + mLastOrientation = getResources().getConfiguration().orientation; } @Override @@ -498,6 +502,19 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard return getCurrentSecurityController(); } + /** + * Apply keyguard configuration from the currently active resources. This can be called when the + * device configuration changes, to re-apply some resources that are qualified on the device + * configuration. + */ + public void updateResources() { + int newOrientation = getResources().getConfiguration().orientation; + if (newOrientation != mLastOrientation) { + mLastOrientation = newOrientation; + mView.updateLayoutForSecurityMode(mCurrentSecurityMode); + } + } + static class Factory { private final KeyguardSecurityContainer mView; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index b4858f42c25b..fd0c4ef0a5be 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -43,16 +43,16 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { private final List<AccessibilityTarget> mTargets; @IntDef({ - AccessibilityTargetAdapter.FIRST_ITEM, - AccessibilityTargetAdapter.REGULAR_ITEM, - AccessibilityTargetAdapter.LAST_ITEM + ItemType.FIRST_ITEM, + ItemType.REGULAR_ITEM, + ItemType.LAST_ITEM }) @Retention(RetentionPolicy.SOURCE) - @interface ItemType {} - - private static final int FIRST_ITEM = 0; - private static final int REGULAR_ITEM = 1; - private static final int LAST_ITEM = 2; + @interface ItemType { + int FIRST_ITEM = 0; + int REGULAR_ITEM = 1; + int LAST_ITEM = 2; + } public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) { mTargets = targets; @@ -65,11 +65,11 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { R.layout.accessibility_floating_menu_item, parent, /* attachToRoot= */ false); - if (itemType == FIRST_ITEM) { + if (itemType == ItemType.FIRST_ITEM) { return new TopViewHolder(root); } - if (itemType == LAST_ITEM) { + if (itemType == ItemType.LAST_ITEM) { return new BottomViewHolder(root); } @@ -87,14 +87,14 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { @Override public int getItemViewType(int position) { if (position == 0) { - return FIRST_ITEM; + return ItemType.FIRST_ITEM; } if (position == (getItemCount() - 1)) { - return LAST_ITEM; + return ItemType.LAST_ITEM; } - return REGULAR_ITEM; + return ItemType.REGULAR_ITEM; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 7e7cdce058bd..6812f77eb159 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -46,7 +46,6 @@ import java.util.Queue; import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; @@ -107,11 +106,14 @@ public class BrightLineFalsingManager implements FalsingManager { } }; - private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener = - new FalsingDataProvider.GestureCompleteListener() { + private final FalsingDataProvider.GestureFinalizedListener mGestureFinalizedListener = + new FalsingDataProvider.GestureFinalizedListener() { @Override - public void onGestureComplete(long completionTimeMs) { + public void onGestureFinalized(long completionTimeMs) { if (mPriorResults != null) { + boolean boolResult = mPriorResults.stream().anyMatch( + FalsingClassifier.Result::isFalse); + mPriorResults.forEach(result -> { if (result.isFalse()) { String reason = result.getReason(); @@ -121,8 +123,28 @@ public class BrightLineFalsingManager implements FalsingManager { } }); + if (Build.IS_ENG || Build.IS_USERDEBUG) { + // Copy motion events, as the results returned by + // #getRecentMotionEvents are recycled elsewhere. + RECENT_SWIPES.add(new DebugSwipeRecord( + boolResult, + mPriorInteractionType, + mDataProvider.getRecentMotionEvents().stream().map( + motionEvent -> new XYDt( + (int) motionEvent.getX(), + (int) motionEvent.getY(), + (int) (motionEvent.getEventTime() + - motionEvent.getDownTime()))) + .collect(Collectors.toList()))); + while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) { + RECENT_SWIPES.remove(); + } + } + + mHistoryTracker.addResults(mPriorResults, completionTimeMs); mPriorResults = null; + mPriorInteractionType = Classifier.GENERIC; } else { // Gestures that were not classified get treated as a false. mHistoryTracker.addResults( @@ -135,6 +157,7 @@ public class BrightLineFalsingManager implements FalsingManager { }; private Collection<FalsingClassifier.Result> mPriorResults; + private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC; @Inject public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, @@ -154,7 +177,7 @@ public class BrightLineFalsingManager implements FalsingManager { mTestHarness = testHarness; mDataProvider.addSessionListener(mSessionListener); - mDataProvider.addGestureCompleteListener(mGestureCompleteListener); + mDataProvider.addGestureCompleteListener(mGestureFinalizedListener); mHistoryTracker.addBeliefListener(mBeliefListener); } @@ -165,50 +188,33 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public boolean isFalseTouch(@Classifier.InteractionType int interactionType) { + mPriorInteractionType = interactionType; if (skipFalsing()) { return false; } - boolean result; + final boolean booleanResult; if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) { - Stream<FalsingClassifier.Result> results = - mClassifiers.stream().map(falsingClassifier -> - falsingClassifier.classifyGesture( - interactionType, - mHistoryTracker.falseBelief(), - mHistoryTracker.falseConfidence())); - mPriorResults = new ArrayList<>(); final boolean[] localResult = {false}; - results.forEach(classifierResult -> { - localResult[0] |= classifierResult.isFalse(); - mPriorResults.add(classifierResult); - }); - result = localResult[0]; + mPriorResults = mClassifiers.stream().map(falsingClassifier -> { + FalsingClassifier.Result r = falsingClassifier.classifyGesture( + interactionType, + mHistoryTracker.falseBelief(), + mHistoryTracker.falseConfidence()); + localResult[0] |= r.isFalse(); + + return r; + }).collect(Collectors.toList()); + booleanResult = localResult[0]; } else { - result = false; + booleanResult = false; mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1)); } - logDebug("False Gesture: " + result); - - if (Build.IS_ENG || Build.IS_USERDEBUG) { - // Copy motion events, as the passed in list gets emptied out elsewhere in the code. - RECENT_SWIPES.add(new DebugSwipeRecord( - result, - interactionType, - mDataProvider.getRecentMotionEvents().stream().map( - motionEvent -> new XYDt( - (int) motionEvent.getX(), - (int) motionEvent.getY(), - (int) (motionEvent.getEventTime() - motionEvent.getDownTime()))) - .collect(Collectors.toList()))); - while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) { - RECENT_SWIPES.remove(); - } - } + logDebug("False Gesture: " + booleanResult); - return result; + return booleanResult; } @Override @@ -354,7 +360,7 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public void cleanup() { mDataProvider.removeSessionListener(mSessionListener); - mDataProvider.removeGestureCompleteListener(mGestureCompleteListener); + mDataProvider.removeGestureCompleteListener(mGestureFinalizedListener); mClassifiers.forEach(FalsingClassifier::cleanup); mFalsingBeliefListeners.clear(); mHistoryTracker.removeBeliefListener(mBeliefListener); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index f665a7439ebe..1aaa139eb7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -45,7 +45,7 @@ public class FalsingDataProvider { private final float mYdpi; private final List<SessionListener> mSessionListeners = new ArrayList<>(); private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>(); - private final List<GestureCompleteListener> mGestureCompleteListeners = new ArrayList<>(); + private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>(); private TimeLimitedMotionEventBuffer mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS); @@ -90,7 +90,7 @@ public class FalsingDataProvider { mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent)); - // We explicitly do not complete a gesture on UP or CANCEL events. + // We explicitly do not "finalize" a gesture on UP or CANCEL events. // We wait for the next gesture to start before marking the prior gesture as complete. This // has multiple benefits. First, it makes it trivial to track the "current" or "recent" // gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly, @@ -102,7 +102,7 @@ public class FalsingDataProvider { private void completePriorGesture() { if (!mRecentMotionEvents.isEmpty()) { - mGestureCompleteListeners.forEach(listener -> listener.onGestureComplete( + mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized( mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime())); mPriorMotionEvents = mRecentMotionEvents; @@ -312,14 +312,14 @@ public class FalsingDataProvider { mMotionEventListeners.remove(listener); } - /** Register a {@link GestureCompleteListener}. */ - public void addGestureCompleteListener(GestureCompleteListener listener) { - mGestureCompleteListeners.add(listener); + /** Register a {@link GestureFinalizedListener}. */ + public void addGestureCompleteListener(GestureFinalizedListener listener) { + mGestureFinalizedListeners.add(listener); } - /** Unregister a {@link GestureCompleteListener}. */ - public void removeGestureCompleteListener(GestureCompleteListener listener) { - mGestureCompleteListeners.remove(listener); + /** Unregister a {@link GestureFinalizedListener}. */ + public void removeGestureCompleteListener(GestureFinalizedListener listener) { + mGestureFinalizedListeners.remove(listener); } void onSessionStarted() { @@ -362,8 +362,12 @@ public class FalsingDataProvider { } /** Callback to be alerted when the current gesture ends. */ - public interface GestureCompleteListener { - /** */ - void onGestureComplete(long completionTimeMs); + public interface GestureFinalizedListener { + /** + * Called just before a new gesture starts. + * + * Any pending work on a prior gesture can be considered cemented in place. + */ + void onGestureFinalized(long completionTimeMs); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index dd3d02a86672..31e49390b9b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -16,38 +16,64 @@ package com.android.systemui.media.systemsounds; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.media.AudioManager; +import android.util.Slog; +import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.shared.system.ActivityManagerWrapper; 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. + * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a + * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions + * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton public class HomeSoundEffectController extends SystemUI { + private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; private final TaskStackChangeListeners mTaskStackChangeListeners; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final PackageManager mPm; + private final boolean mPlayHomeSoundAfterAssistant; + private final boolean mPlayHomeSoundAfterDream; // Initialize true because home sound should not be played when the system boots. private boolean mIsLastTaskHome = true; + // mLastHomePackageName could go out of sync in rare circumstances if launcher changes, + // but it's cheaper than the alternative and potential impact is low + private String mLastHomePackageName; + private @WindowConfiguration.ActivityType int mLastActivityType; + private boolean mLastActivityHasNoHomeSound = false; + private int mLastTaskId; @Inject public HomeSoundEffectController( Context context, AudioManager audioManager, - TaskStackChangeListeners taskStackChangeListeners) { + TaskStackChangeListeners taskStackChangeListeners, + ActivityManagerWrapper activityManagerWrapper, + PackageManager packageManager) { super(context); mAudioManager = audioManager; mTaskStackChangeListeners = taskStackChangeListeners; + mActivityManagerWrapper = activityManagerWrapper; + mPm = packageManager; + mPlayHomeSoundAfterAssistant = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterAssistant); + mPlayHomeSoundAfterDream = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterDream); } @Override @@ -56,27 +82,94 @@ public class HomeSoundEffectController extends SystemUI { mTaskStackChangeListeners.registerTaskStackListener( new TaskStackChangeListener() { @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - handleHomeTaskMovedToFront(taskInfo); + public void onTaskStackChanged() { + ActivityManager.RunningTaskInfo currentTask = + mActivityManagerWrapper.getRunningTask(); + if (currentTask == null || currentTask.topActivityInfo == null) { + return; + } + handleTaskStackChanged(currentTask); } }); } } - private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME; + private boolean hasFlagNoSound(ActivityInfo activityInfo) { + if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) { + // Only allow flag if app has permission + if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + Slog.w(TAG, + "Activity has flag playHomeTransition set to false but doesn't hold " + + "required permission " + + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS); + return false; + } + } + return false; } /** - * To enable a home sound, check if the home app moves to front. + * The home sound is played if all of the following conditions are met: + * <ul> + * <li>The last task which moved to front was not home. This avoids playing the sound + * e.g. after FallbackHome transitions to home, another activity of the home app like a + * notification panel moved to front, or in case the home app crashed.</li> + * <li>The current activity which moved to front is home</li> + * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set + * to <code>true</code>.</li> + * <li>The topActivity of the last task is not of type + * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is + * set to <code>false</code> (default).</li> + * <li>The topActivity of the last task is not of type + * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is + * set to <code>false</code> (default).</li> + * </ul> */ - 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) { + private boolean shouldPlayHomeSoundForCurrentTransition( + ActivityManager.RunningTaskInfo currentTask) { + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + if (currentTask.taskId == mLastTaskId) { + return false; + } + if (mIsLastTaskHome || !isHomeActivity) { + return false; + } + if (mLastActivityHasNoHomeSound) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT + && !mPlayHomeSoundAfterAssistant) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM + && !mPlayHomeSoundAfterDream) { + return false; + } + return true; + } + + private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) { + mLastTaskId = currentTask.taskId; + mLastActivityType = currentTask.topActivityType; + mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo); + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + boolean isHomePackage = currentTask.topActivityInfo.packageName.equals( + mLastHomePackageName); + mIsLastTaskHome = isHomeActivity || isHomePackage; + if (isHomeActivity && !isHomePackage) { + mLastHomePackageName = currentTask.topActivityInfo.packageName; + } + } + + private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) { + if (shouldPlayHomeSoundForCurrentTransition(frontTask)) { mAudioManager.playSoundEffect(AudioManager.FX_HOME); } - mIsLastTaskHome = isCurrentTaskHome; + updateLastTaskInfo(frontTask); } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index bc196bf68b66..8d1b712e0807 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -361,10 +361,13 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.image, View.GONE); views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message); } - if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) { + if (mTile.getMessagesCount() > 1) { views.setViewVisibility(R.id.messages_count, View.VISIBLE); views.setTextViewText(R.id.messages_count, getMessagesCountText(mTile.getMessagesCount())); + if (mLayoutSize == LAYOUT_SMALL) { + views.setViewVisibility(R.id.predefined_icon, View.GONE); + } } // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and // subtract 1 from maxLines when present. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 9967936ac1bd..05bc6e2a5e1e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.drawable.Animatable; import android.util.AttributeSet; import android.util.SparseArray; @@ -140,7 +141,10 @@ public class QSDetail extends LinearLayout { private void updateDetailText() { mDetailDoneButton.setText(R.string.quick_settings_done); - mDetailSettingsButton.setText(R.string.quick_settings_more_settings); + final int resId = + mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL; + mDetailSettingsButton.setText( + (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings); } public void updateResources() { @@ -218,6 +222,7 @@ public class QSDetail extends LinearLayout { mQsPanelController.setGridContentVisibility(true); mQsPanelCallback.onScanStateChanged(false); } + updateDetailText(); sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); if (mShouldAnimate) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 6d019d2aa6f5..badffce3259a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -38,7 +38,6 @@ import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.Notification; -import android.app.WindowContext; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -74,6 +73,7 @@ import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; +import android.window.WindowContext; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; 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 0da441d1ad35..83558cbf089f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -822,6 +822,11 @@ public class UserSwitcherController implements Dumpable { } @Override + public int getSettingsText() { + return R.string.quick_settings_more_user_settings; + } + + @Override public Boolean getToggleState() { return null; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 5dc7006406ee..044d8285b079 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -546,9 +546,10 @@ public class VolumeDialogImpl implements VolumeDialog, row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( R.id.volume_seekbar_background_solid); - row.sliderBgIcon = (AlphaTintDrawableWrapper) - ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( - R.id.volume_seekbar_background_icon)).getDrawable(); + final Drawable sliderBgIcon = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon); + row.sliderBgIcon = sliderBgIcon != null ? (AlphaTintDrawableWrapper) + ((RotateDrawable) sliderBgIcon).getDrawable() : null; final LayerDrawable seekbarProgressDrawable = (LayerDrawable) ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( @@ -556,13 +557,12 @@ public class VolumeDialogImpl implements VolumeDialog, row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( R.id.volume_seekbar_progress_solid); - - row.sliderProgressIcon = (AlphaTintDrawableWrapper) - ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( - R.id.volume_seekbar_progress_icon)).getDrawable(); + final Drawable sliderProgressIcon = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon); + row.sliderProgressIcon = sliderProgressIcon != null ? (AlphaTintDrawableWrapper) + ((RotateDrawable) sliderProgressIcon).getDrawable() : null; row.slider.setProgressDrawable(seekbarDrawable); - row.slider.setThumb(null); row.icon = row.view.findViewById(R.id.volume_row_icon); @@ -1484,10 +1484,14 @@ public class VolumeDialogImpl implements VolumeDialog, mContext, android.R.attr.colorBackgroundFloating); row.sliderProgressSolid.setTintList(colorTint); - row.sliderBgIcon.setTintList(colorTint); + if (row.sliderBgIcon != null) { + row.sliderBgIcon.setTintList(colorTint); + } row.sliderBgSolid.setTintList(bgTint); - row.sliderProgressIcon.setTintList(bgTint); + if (row.sliderProgressIcon != null) { + row.sliderProgressIcon.setTintList(bgTint); + } if (row.icon != null) { row.icon.setImageTintList(colorTint); @@ -1878,8 +1882,12 @@ public class VolumeDialogImpl implements VolumeDialog, icon.setImageResource(iconRes); } - sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); - sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + if (sliderProgressIcon != null) { + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } + if (sliderBgIcon != null) { + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index ddfa63a33149..709ccd4c2384 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -16,26 +16,18 @@ package com.android.systemui.wmshell; -import static android.os.Process.THREAD_PRIORITY_DISPLAY; -import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; - -import android.animation.AnimationHandler; import android.app.ActivityTaskManager; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Handler; -import android.os.HandlerThread; import android.view.IWindowManager; import android.view.WindowManager; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.R; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.FullscreenTaskListener; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellCommandHandler; @@ -53,13 +45,11 @@ import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.FloatingContentCoordinator; -import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; @@ -99,110 +89,9 @@ import dagger.Provides; * dependencies that are device/form factor SystemUI implementation specific should go into their * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.) */ -@Module +@Module(includes = WMShellConcurrencyModule.class) public abstract class WMShellBaseModule { - /** - * Returns whether to enable a separate shell thread for the shell features. - */ - private static boolean enableShellMainThread(Context context) { - return context.getResources().getBoolean(R.bool.config_enableShellMainThread); - } - - // - // Shell Concurrency - Components used for managing threading in the Shell and SysUI - // - - /** - * Provide a SysUI main-thread Executor. - */ - @WMSingleton - @Provides - @Main - public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) { - return new HandlerExecutor(sysuiMainHandler); - } - - /** - * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe - * multiple types of messages, etc.) - */ - @WMSingleton - @Provides - @ShellMainThread - public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { - if (enableShellMainThread(context)) { - HandlerThread mainThread = new HandlerThread("wmshell.main"); - mainThread.start(); - return mainThread.getThreadHandler(); - } - return sysuiMainHandler; - } - - /** - * Provide a Shell main-thread Executor. - */ - @WMSingleton - @Provides - @ShellMainThread - public static ShellExecutor provideShellMainExecutor(Context context, - @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { - if (enableShellMainThread(context)) { - return new HandlerExecutor(mainHandler); - } - return sysuiMainExecutor; - } - - /** - * Provide a Shell animation-thread Executor. - */ - @WMSingleton - @Provides - @ShellAnimationThread - public static ShellExecutor provideShellAnimationExecutor() { - HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", - THREAD_PRIORITY_DISPLAY); - shellAnimationThread.start(); - return new HandlerExecutor(shellAnimationThread.getThreadHandler()); - } - - /** - * Provides a Shell splashscreen-thread Executor - */ - @WMSingleton - @Provides - @ShellSplashscreenThread - public static ShellExecutor provideSplashScreenExecutor() { - HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen", - THREAD_PRIORITY_TOP_APP_BOOST); - shellSplashscreenThread.start(); - return new HandlerExecutor(shellSplashscreenThread.getThreadHandler()); - } - - /** - * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on - * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on - * the Shell main-thread with the SF vsync. - */ - @WMSingleton - @Provides - @ChoreographerSfVsync - public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler( - @ShellMainThread ShellExecutor mainExecutor) { - try { - AnimationHandler handler = new AnimationHandler(); - mainExecutor.executeBlocking(() -> { - // This is called on the animation thread since it calls - // Choreographer.getSfInstance() which returns a thread-local Choreographer instance - // that uses the SF vsync - handler.setProvider(new SfVsyncFrameCallbackProvider()); - }); - return handler; - } catch (InterruptedException e) { - throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); - } - } - // // Internal common - Components used internally by multiple shell features // diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java new file mode 100644 index 000000000000..61f50b5aae30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java @@ -0,0 +1,163 @@ +/* + * 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.wmshell; + +import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; + +import android.animation.AnimationHandler; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Trace; + +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.systemui.R; +import com.android.systemui.dagger.WMSingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.wm.shell.common.HandlerExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ChoreographerSfVsync; +import com.android.wm.shell.common.annotations.ShellAnimationThread; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.annotations.ShellSplashscreenThread; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides basic concurrency-related dependencies from {@link com.android.wm.shell}, these + * dependencies are only accessible from components within the WM subcomponent. + */ +@Module +public abstract class WMShellConcurrencyModule { + + private static final int MSGQ_SLOW_DELIVERY_THRESHOLD_MS = 30; + private static final int MSGQ_SLOW_DISPATCH_THRESHOLD_MS = 30; + + /** + * Returns whether to enable a separate shell thread for the shell features. + */ + private static boolean enableShellMainThread(Context context) { + return context.getResources().getBoolean(R.bool.config_enableShellMainThread); + } + + // + // Shell Concurrency - Components used for managing threading in the Shell and SysUI + // + + /** + * Provide a SysUI main-thread Executor. + */ + @WMSingleton + @Provides + @Main + public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) { + return new HandlerExecutor(sysuiMainHandler); + } + + /** + * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe + * multiple types of messages, etc.) + */ + @WMSingleton + @Provides + @ShellMainThread + public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { + if (enableShellMainThread(context)) { + HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY); + mainThread.start(); + if (Build.IS_DEBUGGABLE) { + mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + MSGQ_SLOW_DELIVERY_THRESHOLD_MS); + } + return Handler.createAsync(mainThread.getLooper()); + } + return sysuiMainHandler; + } + + /** + * Provide a Shell main-thread Executor. + */ + @WMSingleton + @Provides + @ShellMainThread + public static ShellExecutor provideShellMainExecutor(Context context, + @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { + if (enableShellMainThread(context)) { + return new HandlerExecutor(mainHandler); + } + return sysuiMainExecutor; + } + + /** + * Provide a Shell animation-thread Executor. + */ + @WMSingleton + @Provides + @ShellAnimationThread + public static ShellExecutor provideShellAnimationExecutor() { + HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", + THREAD_PRIORITY_DISPLAY); + shellAnimationThread.start(); + if (Build.IS_DEBUGGABLE) { + shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + MSGQ_SLOW_DELIVERY_THRESHOLD_MS); + } + return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper())); + } + + /** + * Provides a Shell splashscreen-thread Executor + */ + @WMSingleton + @Provides + @ShellSplashscreenThread + public static ShellExecutor provideSplashScreenExecutor() { + HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen", + THREAD_PRIORITY_TOP_APP_BOOST); + shellSplashscreenThread.start(); + return new HandlerExecutor(shellSplashscreenThread.getThreadHandler()); + } + + /** + * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on + * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on + * the Shell main-thread with the SF vsync. + */ + @WMSingleton + @Provides + @ChoreographerSfVsync + public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler( + @ShellMainThread ShellExecutor mainExecutor) { + try { + AnimationHandler handler = new AnimationHandler(); + mainExecutor.executeBlocking(() -> { + // This is called on the animation thread since it calls + // Choreographer.getSfInstance() which returns a thread-local Choreographer instance + // that uses the SF vsync + handler.setProvider(new SfVsyncFrameCallbackProvider()); + }); + return handler; + } catch (InterruptedException e) { + throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 084c0b496c16..99b3f6f19f81 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -25,9 +25,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Configuration; import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -96,6 +98,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private EmergencyButtonController mEmergencyButtonController; + @Mock + private Resources mResources; + private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; private KeyguardPasswordViewController mKeyguardPasswordViewController; @@ -103,6 +108,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Before public void setup() { + mConfiguration = new Configuration(); + mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED. + + when(mResources.getConfiguration()).thenReturn(mConfiguration); + when(mView.getResources()).thenReturn(mResources); when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) .thenReturn(mAdminSecondaryLockScreenController); when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); @@ -154,4 +164,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { verify(mWindowInsetsController).controlWindowInsetsAnimation( eq(ime()), anyLong(), any(), any(), any()); } + + @Test + public void onResourcesUpdate_callsThroughOnRotationChange() { + // Rotation is the same, shouldn't cause an update + mKeyguardSecurityContainerController.updateResources(); + verify(mView, times(0)).updateLayoutForSecurityMode(any()); + + // Update rotation. Should trigger update + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + + mKeyguardSecurityContainerController.updateResources(); + verify(mView, times(1)).updateLayoutForSecurityMode(any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 67c1d075d03e..1f165bba2bf7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -34,7 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener; +import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener; import com.android.systemui.dock.DockManagerFake; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -82,7 +82,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), ""); private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1); - private GestureCompleteListener mGestureCompleteListener; + private GestureFinalizedListener mGestureFinalizedListener; @Before public void setup() { @@ -103,13 +103,13 @@ public class BrightLineClassifierTest extends SysuiTestCase { mHistoryTracker, mKeyguardStateController, false); - ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor = - ArgumentCaptor.forClass(GestureCompleteListener.class); + ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor = + ArgumentCaptor.forClass(GestureFinalizedListener.class); verify(mFalsingDataProvider).addGestureCompleteListener( gestureCompleteListenerCaptor.capture()); - mGestureCompleteListener = gestureCompleteListenerCaptor.getValue(); + mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); } @Test @@ -211,7 +211,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { @Test public void testHistory() { - mGestureCompleteListener.onGestureComplete(1000); + mGestureFinalizedListener.onGestureFinalized(1000); verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); } @@ -220,7 +220,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { public void testHistory_singleTap() { // When trying to classify single taps, we don't immediately add results to history. mBrightLineFalsingManager.isFalseTap(false, 0); - mGestureCompleteListener.onGestureComplete(1000); + mGestureFinalizedListener.onGestureFinalized(1000); verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); } @@ -228,9 +228,9 @@ public class BrightLineClassifierTest extends SysuiTestCase { public void testHistory_multipleSingleTaps() { // When trying to classify single taps, we don't immediately add results to history. mBrightLineFalsingManager.isFalseTap(false, 0); - mGestureCompleteListener.onGestureComplete(1000); + mGestureFinalizedListener.onGestureFinalized(1000); mBrightLineFalsingManager.isFalseTap(false, 0); - mGestureCompleteListener.onGestureComplete(2000); + mGestureFinalizedListener.onGestureFinalized(2000); verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); verify(mHistoryTracker).addResults(anyCollection(), eq(2000L)); } @@ -239,11 +239,11 @@ public class BrightLineClassifierTest extends SysuiTestCase { public void testHistory_doubleTap() { // When trying to classify single taps, we don't immediately add results to history. mBrightLineFalsingManager.isFalseTap(false, 0); - mGestureCompleteListener.onGestureComplete(1000); + mGestureFinalizedListener.onGestureFinalized(1000); // Before checking for double tap, we may check for single-tap on the second gesture. mBrightLineFalsingManager.isFalseTap(false, 0); mBrightLineFalsingManager.isFalseDoubleTap(); - mGestureCompleteListener.onGestureComplete(2000); + mGestureFinalizedListener.onGestureFinalized(2000); // Double tap is immediately added to history. Single tap is never added. verify(mHistoryTracker).addResults(anyCollection(), eq(2000L)); 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 index 3a77f7eec7f9..33a30e0bcc27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java @@ -21,16 +21,20 @@ 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 static org.mockito.Mockito.when; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; -import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; 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.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -41,30 +45,50 @@ 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 static final String HOME_PACKAGE_NAME = "com.android.apps.home"; + private static final String NON_HOME_PACKAGE_NAME = "com.android.apps.not.home"; + private static final int HOME_TASK_ID = 0; + private static final int NON_HOME_TASK_ID = 1; + private @Mock AudioManager mAudioManager; private @Mock TaskStackChangeListeners mTaskStackChangeListeners; - private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo; - private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo; - + private @Mock ActivityManagerWrapper mActivityManagerWrapper; + private @Mock PackageManager mPackageManager; + + private ActivityManager.RunningTaskInfo mTaskAStandardActivity; + private ActivityManager.RunningTaskInfo mTaskAExceptionActivity; + private ActivityManager.RunningTaskInfo mHomeTaskHomeActivity; + private ActivityManager.RunningTaskInfo mHomeTaskStandardActivity; + private ActivityManager.RunningTaskInfo mEmptyTask; 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(); - + mTaskAStandardActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + true /* playHomeTransitionSound */); + mTaskAExceptionActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + false /* playHomeTransitionSound */); + mHomeTaskHomeActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_HOME, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mHomeTaskStandardActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mEmptyTask = new ActivityManager.RunningTaskInfo(); + mContext.setMockPackageManager(mPackageManager); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED); mController = new HomeSoundEffectController(mContext, mAudioManager, - mTaskStackChangeListeners); + mTaskStackChangeListeners, mActivityManagerWrapper, mPackageManager); } @Test @@ -73,43 +97,60 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME + * Expectation: Home sound is played + */ @Test public void testHomeSoundEffectPlayedWhenEnabled() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then the home sound effect should be played. verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME + * Expectation: Home sound is played once after HOME moves to front the first time + */ @Test public void testHomeSoundEffectNotPlayedTwiceInRow() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskHomeActivity); + // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // 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); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); @@ -121,7 +162,59 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And a standard, non-home task, moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mTaskAStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME (activity type standard) + * Expectation: Home sound is played once after HOME moves to front + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFront() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskStandardActivity); + + // And first a task different from the home task moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // 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.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME (activity type standard) + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFrontOfOtherApp() { + // 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, + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And an activity from the home package but not the home root activity moves to front + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); @@ -138,6 +231,171 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { } /** + * Task A (playHomeTransitionSound = false) -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException() { + // 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, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played because the last package is an exception. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (playHomeTransitionSound = false) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException2() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (playHomeTransitionSound = true) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenHomeActivityMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mTaskAStandardActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, + // the topActivity of this task has {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} + // set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenEmptyTaskMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenEmptyTaskMovesToFrontAfterStandardActivity() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false, no permission) -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenFlagSetButPermissionNotGranted() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_DENIED); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>, + // but the app doesn't have the right permission granted + mTaskStackChangeListener.onTaskStackChanged(); + + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** * Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController. * If the home sound effect is enabled, the registered TaskStackChangeListener is extracted. */ @@ -155,4 +413,20 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { mTaskStackChangeListener = listenerCaptor.getValue(); } } + + private ActivityManager.RunningTaskInfo createRunningTaskInfo(String packageName, + int activityType, int taskId, boolean playHomeTransitionSound) { + ActivityManager.RunningTaskInfo res = new ActivityManager.RunningTaskInfo(); + res.topActivityInfo = new ActivityInfo(); + res.topActivityInfo.packageName = packageName; + res.topActivityType = activityType; + res.taskId = taskId; + if (!playHomeTransitionSound) { + // set the flag to 0 + res.topActivityInfo.privateFlags &= ~ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } else { + res.topActivityInfo.privateFlags |= ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } + return res; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java index c2e0d6b49a76..8db0f33ba7e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java @@ -453,6 +453,9 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); assertThat(statusContent.getMaxLines()).isEqualTo(3); + // Has a single message, no count shown. + assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility()); + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_width_for_medium) - 1); RemoteViews smallView = new PeopleTileViewHelper(mContext, @@ -492,6 +495,10 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { assertEquals(View.VISIBLE, statusContent.getVisibility()); assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); assertThat(statusContent.getMaxLines()).isEqualTo(3); + + // Has a single message, no count shown. + assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility()); + } @Test @@ -507,19 +514,62 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { TextView name = (TextView) result.findViewById(R.id.name); assertEquals(name.getText(), NAME); - TextView subtext = (TextView) result.findViewById(R.id.subtext); - assertEquals(View.GONE, subtext.getVisibility()); + assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility()); + assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility()); // Has availability. - View availability = result.findViewById(R.id.availability); - assertEquals(View.VISIBLE, availability.getVisibility()); + assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); // Has person icon. - View personIcon = result.findViewById(R.id.person_icon); - assertEquals(View.VISIBLE, personIcon.getVisibility()); + assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility()); // Has notification content. TextView statusContent = (TextView) result.findViewById(R.id.text_content); + assertEquals(View.VISIBLE, statusContent.getVisibility()); + assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); + assertThat(statusContent.getMaxLines()).isEqualTo(3); + + // Has two messages, show count. + assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility()); + + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_width_for_medium) - 1); + RemoteViews smallView = new PeopleTileViewHelper(mContext, + tileWithStatusAndNotification, 0, mOptions).getViews(); + View smallResult = smallView.apply(mContext, null); + + // Show icon instead of name. + assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility()); + assertEquals(View.GONE, + smallResult.findViewById(R.id.predefined_icon).getVisibility()); + // Has person icon. + assertEquals(View.VISIBLE, + smallResult.findViewById(R.id.person_icon).getVisibility()); + + // Has two messages, show count. + assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility()); + + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_width_for_large)); + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_height_for_large)); + RemoteViews largeView = new PeopleTileViewHelper(mContext, + tileWithStatusAndNotification, 0, mOptions).getViews(); + View largeResult = largeView.apply(mContext, null); + + name = (TextView) largeResult.findViewById(R.id.name); + assertEquals(name.getText(), NAME); + assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility()); + assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility()); + // Has availability. + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility()); + // Has person icon. + View personIcon = largeResult.findViewById(R.id.person_icon); + assertEquals(View.VISIBLE, personIcon.getVisibility()); + // Has notification content. + statusContent = (TextView) largeResult.findViewById(R.id.text_content); + assertEquals(View.VISIBLE, statusContent.getVisibility()); assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); + assertThat(statusContent.getMaxLines()).isEqualTo(3); - // Has 2 messages, show count. + // Has two messages, show count. assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility()); } diff --git a/services/core/Android.bp b/services/core/Android.bp index 5f7016e724ef..641b38db0c07 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -235,6 +235,7 @@ filegroup { "java/com/android/server/connectivity/NetworkAgentInfo.java", "java/com/android/server/connectivity/NetworkDiagnostics.java", "java/com/android/server/connectivity/NetworkNotificationManager.java", + "java/com/android/server/connectivity/NetworkOffer.java", "java/com/android/server/connectivity/NetworkRanker.java", "java/com/android/server/connectivity/OsCompat.java", "java/com/android/server/connectivity/PermissionMonitor.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 59a1a7817cfd..8f568420fb7e 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -15,7 +15,6 @@ */ package com.android.server; - import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.FEATURE_BLUETOOTH; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -30,6 +29,7 @@ import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTI import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS; import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS; import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.TYPE_BLUETOOTH; @@ -75,7 +75,6 @@ import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired; @@ -122,6 +121,7 @@ import android.net.INetworkActivityListener; import android.net.INetworkAgent; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; +import android.net.INetworkOfferCallback; import android.net.IOnCompleteListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -229,6 +229,7 @@ import com.android.net.module.util.PermissionUtils; import com.android.server.connectivity.AutodestructReference; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; +import com.android.server.connectivity.FullScore; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; @@ -236,6 +237,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import com.android.server.connectivity.NetworkOffer; import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProfileNetworkPreferences; @@ -593,6 +595,18 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51; /** + * Event to register a new network offer + * obj = NetworkOffer + */ + private static final int EVENT_REGISTER_NETWORK_OFFER = 52; + + /** + * Event to unregister an existing network offer + * obj = INetworkOfferCallback + */ + private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -4565,6 +4579,18 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUnregisterNetworkProvider((Messenger) msg.obj); break; } + case EVENT_REGISTER_NETWORK_OFFER: { + handleRegisterNetworkOffer((NetworkOffer) msg.obj); + break; + } + case EVENT_UNREGISTER_NETWORK_OFFER: { + final NetworkOfferInfo offer = + findNetworkOfferInfoByCallback((INetworkOfferCallback) msg.obj); + if (null != offer) { + handleUnregisterNetworkOffer(offer); + } + break; + } case EVENT_REGISTER_NETWORK_AGENT: { final Pair<NetworkAgentInfo, INetworkMonitor> arg = (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj; @@ -6046,12 +6072,37 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } + @Override + public void offerNetwork(final int providerId, + @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, + @NonNull final INetworkOfferCallback callback) { + final NetworkOffer offer = new NetworkOffer( + FullScore.makeProspectiveScore(score, caps), caps, callback, providerId); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer)); + } + + @Override + public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_OFFER, callback)); + } + private void handleUnregisterNetworkProvider(Messenger messenger) { NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); if (npi == null) { loge("Failed to find Messenger in unregisterNetworkProvider"); return; } + // Unregister all the offers from this provider + final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>(); + for (final NetworkOfferInfo noi : mNetworkOffers) { + if (noi.offer.providerId == npi.providerId) { + // Can't call handleUnregisterNetworkOffer here because iteration is in progress + toRemove.add(noi); + } + } + for (NetworkOfferInfo noi : toRemove) { + handleUnregisterNetworkOffer(noi); + } if (DBG) log("unregisterNetworkProvider for " + npi.name); } @@ -6090,6 +6141,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // (on the handler thread). private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>(); + // Must only be accessed on the handler thread + @NonNull + private final ArrayList<NetworkOfferInfo> mNetworkOffers = new ArrayList<>(); + @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); @@ -6399,6 +6454,71 @@ public class ConnectivityService extends IConnectivityManager.Stub updateUids(nai, null, nai.networkCapabilities); } + private class NetworkOfferInfo implements IBinder.DeathRecipient { + @NonNull public final NetworkOffer offer; + + NetworkOfferInfo(@NonNull final NetworkOffer offer) { + this.offer = offer; + } + + @Override + public void binderDied() { + mHandler.post(() -> handleUnregisterNetworkOffer(this)); + } + } + + private boolean isNetworkProviderWithIdRegistered(final int providerId) { + for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + if (npi.providerId == providerId) return true; + } + return false; + } + + /** + * Register or update a network offer. + * @param newOffer The new offer. If the callback member is the same as an existing + * offer, it is an update of that offer. + */ + private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) { + ensureRunningOnConnectivityServiceThread(); + if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) { + // This may actually happen if a provider updates its score or registers and then + // immediately unregisters. The offer would still be in the handler queue, but the + // provider would have been removed. + if (DBG) log("Received offer from an unregistered provider"); + return; + } + final NetworkOfferInfo existingOffer = findNetworkOfferInfoByCallback(newOffer.callback); + if (null != existingOffer) { + handleUnregisterNetworkOffer(existingOffer); + newOffer.migrateFrom(existingOffer.offer); + } + final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer); + try { + noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */); + } catch (RemoteException e) { + noi.binderDied(); + return; + } + mNetworkOffers.add(noi); + // TODO : send requests to the provider. + } + + private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) { + ensureRunningOnConnectivityServiceThread(); + mNetworkOffers.remove(noi); + noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */); + } + + @Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback( + @NonNull final INetworkOfferCallback callback) { + ensureRunningOnConnectivityServiceThread(); + for (final NetworkOfferInfo noi : mNetworkOffers) { + if (noi.offer.callback.equals(callback)) return noi; + } + return null; + } + /** * Called when receiving LinkProperties directly from a NetworkAgent. * Stores into |nai| any data coming from the agent that might also be written to the network's diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 5d1ca3336fa7..09f4c221f3fe 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -143,7 +143,6 @@ import com.android.internal.os.AppFuseMount; import com.android.internal.os.BackgroundThread; import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.os.SomeArgs; -import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; @@ -4268,31 +4267,37 @@ class StorageManagerService extends IStorageManager.Stub } } + @Override + public int getExternalStorageMountMode(int uid, String packageName) { + enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE); + return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName); + } + private int getMountModeInternal(int uid, String packageName) { try { // Get some easy cases out of the way first if (Process.isIsolated(uid)) { - return Zygote.MOUNT_EXTERNAL_NONE; + return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid); if (ArrayUtils.isEmpty(packagesForUid)) { // It's possible the package got uninstalled already, so just ignore. - return Zygote.MOUNT_EXTERNAL_NONE; + return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } if (packageName == null) { packageName = packagesForUid[0]; } if (mPmInternal.isInstantApp(packageName, UserHandle.getUserId(uid))) { - return Zygote.MOUNT_EXTERNAL_NONE; + return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } if (mStorageManagerInternal.isExternalStorageService(uid)) { // Determine if caller requires pass_through mount; note that we do this for // all processes that share a UID with MediaProvider; but this is fine, since // those processes anyway share the same rights as MediaProvider. - return Zygote.MOUNT_EXTERNAL_PASS_THROUGH; + return StorageManager.MOUNT_MODE_EXTERNAL_PASS_THROUGH; } if ((mDownloadsAuthorityAppId == UserHandle.getAppId(uid) @@ -4300,7 +4305,7 @@ class StorageManagerService extends IStorageManager.Stub // DownloadManager can write in app-private directories on behalf of apps; // give it write access to Android/ // ExternalStorageProvider can access Android/{data,obb} dirs in managed mode - return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; + return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE; } final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) == @@ -4310,7 +4315,7 @@ class StorageManagerService extends IStorageManager.Stub 0, UserHandle.getUserId(uid)); if (ai != null && ai.isSignedWithPlatformKey()) { // Platform processes hosting the MTP server should be able to write in Android/ - return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; + return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE; } } @@ -4335,13 +4340,13 @@ class StorageManagerService extends IStorageManager.Stub } } if ((hasInstall || hasInstallOp) && hasWrite) { - return Zygote.MOUNT_EXTERNAL_INSTALLER; + return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER; } - return Zygote.MOUNT_EXTERNAL_DEFAULT; + return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT; } catch (RemoteException e) { // Should not happen } - return Zygote.MOUNT_EXTERNAL_NONE; + return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } private static class Callbacks extends Handler { @@ -4711,7 +4716,8 @@ class StorageManagerService extends IStorageManager.Stub return true; } - return getExternalStorageMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE; + return getExternalStorageMountMode(uid, packageName) + != StorageManager.MOUNT_MODE_EXTERNAL_NONE; } private void killAppForOpChange(int code, int uid) { diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 7276c78b398c..411fc6778984 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -154,6 +154,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + int targetSdk; boolean matchTelephonyCallbackEvent(int event) { return (callback != null) && (this.eventList.contains(event)); @@ -919,6 +920,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } r.phoneId = phoneId; r.eventList = events; + r.targetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage); + if (DBG) { log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); } @@ -1748,6 +1751,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED) && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) { try { + if (r.targetSdk <= android.os.Build.VERSION_CODES.R) { + telephonyDisplayInfo = + getBackwardCompatibleTelephonyDisplayInfo( + telephonyDisplayInfo); + } r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } catch (RemoteException ex) { mRemoveList.add(r.binder); @@ -1759,6 +1767,19 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + private TelephonyDisplayInfo getBackwardCompatibleTelephonyDisplayInfo( + @NonNull TelephonyDisplayInfo telephonyDisplayInfo) { + int networkType = telephonyDisplayInfo.getNetworkType(); + int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType(); + if (networkType == TelephonyManager.NETWORK_TYPE_NR) { + overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE; + } else if (networkType == TelephonyManager.NETWORK_TYPE_LTE + && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) { + overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE; + } + return new TelephonyDisplayInfo(networkType, overrideNetworkType); + } + public void notifyCallForwardingChanged(boolean cfi) { notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi); } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 77cec78ff9a8..cfa110e82e89 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -348,7 +348,8 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } - private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) { + private void enforceCallingUserAndCarrierPrivilege( + ParcelUuid subscriptionGroup, String pkgName) { // Only apps running in the primary (system) user are allowed to configure the VCN. This is // in line with Telephony's behavior with regards to binding to a Carrier App provided // CarrierConfigService. @@ -362,12 +363,15 @@ public class VcnManagementService extends IVcnManagementService.Stub { subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup)); }); - final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class); for (SubscriptionInfo info : subscriptionInfos) { + final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class) + .createForSubscriptionId(info.getSubscriptionId()); + // Check subscription is active first; much cheaper/faster check, and an app (currently) // cannot be carrier privileged for inactive subscriptions. if (subMgr.isValidSlotIndex(info.getSimSlotIndex()) - && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) { + && telMgr.checkCarrierPrivilegesForPackage(pkgName) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { // TODO (b/173717728): Allow configuration for inactive, but manageable // subscriptions. // TODO (b/173718661): Check for whole subscription groups at a time. @@ -535,7 +539,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { mContext.getSystemService(AppOpsManager.class) .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); - enforceCallingUserAndCarrierPrivilege(subscriptionGroup); + enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { @@ -553,11 +557,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { * <p>Implements the IVcnManagementService Binder interface. */ @Override - public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { + public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + requireNonNull(opPkgName, "opPkgName was null"); Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup); - enforceCallingUserAndCarrierPrivilege(subscriptionGroup); + mContext.getSystemService(AppOpsManager.class) + .checkPackage(mDeps.getBinderCallingUid(), opPkgName); + enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d2fd8ff3890e..98931a528d31 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1790,6 +1790,44 @@ public final class ActiveServices { } if (!ignoreForeground) { + if (r.mStartForegroundCount == 0) { + /* + If the service was started with startService(), not + startForegroundService(), and if startForeground() isn't called within + mFgsStartForegroundTimeoutMs, then we check the state of the app + (who owns the service, which is the app that called startForeground()) + again. If the app is in the foreground, or in any other cases where + FGS-starts are allowed, then we still allow the FGS to be started. + Otherwise, startForeground() would fail. + + If the service was started with startForegroundService(), then the service + must call startForeground() within a timeout anyway, so we don't need this + check. + */ + if (!r.fgRequired) { + final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, false); + final String temp = "startForegroundDelayMs:" + delayMs; + if (r.mInfoAllowStartForeground != null) { + r.mInfoAllowStartForeground += "; " + temp; + } else { + r.mInfoAllowStartForeground = temp; + } + r.mLoggedInfoAllowStartForeground = false; + } + } + } else if (r.mStartForegroundCount >= 1) { + // The second or later time startForeground() is called after service is + // started. Check for app state again. + final long delayMs = SystemClock.elapsedRealtime() - + r.mLastSetFgsRestrictionTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, false); + } + } logFgsBackgroundStart(r); if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { final String msg = "Service.startForeground() not allowed due to " @@ -1843,6 +1881,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mStartForegroundCount++; if (!stopProcStatsOp) { ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1901,6 +1940,7 @@ public final class ActiveServices { decActiveForegroundAppLocked(smap, r); } r.isForeground = false; + resetFgsRestrictionLocked(r); ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), @@ -3892,6 +3932,7 @@ public final class ActiveServices { r.foregroundId = 0; r.foregroundNoti = null; r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; // Clear start entries. r.clearDeliveredStartsLocked(); @@ -5430,6 +5471,7 @@ public final class ActiveServices { private void setFgsRestrictionLocked(String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { + r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime(); // Check DeviceConfig flag. if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { r.mAllowWhileInUsePermissionInFgs = true; @@ -5450,6 +5492,15 @@ public final class ActiveServices { } } + void resetFgsRestrictionLocked(ServiceRecord r) { + r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; + r.mInfoAllowStartForeground = null; + r.mInfoTempFgsAllowListReason = null; + r.mLoggedInfoAllowStartForeground = false; + r.mLastSetFgsRestrictionTime = 0; + } + boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) { if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { return true; @@ -5601,6 +5652,7 @@ public final class ActiveServices { + ",callingUid:" + tempAllowListReason.mCallingUid)) + ">" + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + + "; startForegroundCount:" + r.mStartForegroundCount + "]"; if (!debugInfo.equals(r.mInfoAllowStartForeground)) { r.mLoggedInfoAllowStartForeground = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 5859cea2d284..f7abf6a3bfc2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -96,6 +96,7 @@ final class ActivityManagerConstants extends ContentObserver { 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"; static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration"; + static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -135,7 +136,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12; private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000; private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000; - + private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000; // Flag stored in the DeviceConfig API. /** @@ -396,6 +397,12 @@ final class ActivityManagerConstants extends ContentObserver { */ volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION; + /** + * When service started from background, before the timeout it can be promoted to FGS by calling + * Service.startForeground(). + */ + volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS; + private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -586,6 +593,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_FG_TO_BG_FGS_GRACE_DURATION: updateFgToBgFgsGraceDuration(); break; + case KEY_FGS_START_FOREGROUND_TIMEOUT: + updateFgsStartForegroundTimeout(); + break; default: break; } @@ -869,6 +879,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FG_TO_BG_FGS_GRACE_DURATION); } + private void updateFgsStartForegroundTimeout() { + mFgsStartForegroundTimeoutMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_FGS_START_FOREGROUND_TIMEOUT, + DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS); + } + private void updateImperceptibleKillExemptions() { IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear(); IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); @@ -1071,6 +1088,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(mBootTimeTempAllowlistDuration); pw.print(" "); pw.print(KEY_FG_TO_BG_FGS_GRACE_DURATION); pw.print("="); pw.println(mFgToBgFgsGraceDuration); + pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("="); + pw.println(mFgsStartForegroundTimeoutMs); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9cd9902f4995..54c35123fbb9 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -165,9 +165,16 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. @PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED; + // Debug info why mAllowStartForeground is allowed or denied. String mInfoAllowStartForeground; + // Debug info if mAllowStartForeground is allowed because of a temp-allowlist. FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason; + // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup. boolean mLoggedInfoAllowStartForeground; + // The number of times Service.startForeground() is called; + int mStartForegroundCount; + // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set. + long mLastSetFgsRestrictionTime; String stringName; // caching of toString @@ -439,6 +446,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.println(mRecentCallingUid); pw.print(prefix); pw.print("allowStartForeground="); pw.println(mAllowStartForeground); + pw.print(prefix); pw.print("startForegroundCount="); + pw.println(mStartForegroundCount); pw.print(prefix); pw.print("infoAllowStartForeground="); pw.println(mInfoAllowStartForeground); if (delayed) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 109ffe38f332..7bc71051c627 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1922,6 +1922,7 @@ public class AppOpsService extends IAppOpsService.Stub { synchronized (this) { if (mWriteScheduled) { mWriteScheduled = false; + mFastWriteScheduled = false; mHandler.removeCallbacks(mWriteRunner); doWrite = true; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 804550b8782d..2ce60d05e541 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1836,6 +1836,127 @@ public class AudioService extends IAudioService.Stub } } + /** @see AudioManager#isSurroundFormatEnabled(int) */ + @Override + public boolean isSurroundFormatEnabled(int audioFormat) { + if (!isSurroundFormat(audioFormat)) { + Log.w(TAG, "audioFormat to enable is not a surround format."); + return false; + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing WRITE_SETTINGS permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSettingsLock) { + HashSet<Integer> enabledFormats = getEnabledFormats(); + return enabledFormats.contains(audioFormat); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @see AudioManager#setSurroundFormatEnabled(int, boolean) */ + @Override + public boolean setSurroundFormatEnabled(int audioFormat, boolean enabled) { + if (!isSurroundFormat(audioFormat)) { + Log.w(TAG, "audioFormat to enable is not a surround format."); + return false; + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing WRITE_SETTINGS permission"); + } + + HashSet<Integer> enabledFormats = getEnabledFormats(); + if (enabled) { + enabledFormats.add(audioFormat); + } else { + enabledFormats.remove(audioFormat); + } + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSettingsLock) { + Settings.Global.putString(mContentResolver, + Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, + TextUtils.join(",", enabledFormats)); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return true; + } + + /** @see AudioManager#setEncodedSurroundMode(int) */ + @Override + public boolean setEncodedSurroundMode(int mode) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing WRITE_SETTINGS permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSettingsLock) { + Settings.Global.putInt(mContentResolver, + Settings.Global.ENCODED_SURROUND_OUTPUT, + mode); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return true; + } + + /** @see AudioManager#getEncodedSurroundMode() */ + @Override + public int getEncodedSurroundMode() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing WRITE_SETTINGS permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSettingsLock) { + return Settings.Global.getInt(mContentResolver, + Settings.Global.ENCODED_SURROUND_OUTPUT, + AudioManager.ENCODED_SURROUND_OUTPUT_AUTO); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @return the formats that are enabled in global settings */ + private HashSet<Integer> getEnabledFormats() { + HashSet<Integer> formats = new HashSet<>(); + String enabledFormats = Settings.Global.getString(mContentResolver, + Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); + if (enabledFormats != null) { + try { + Arrays.stream(TextUtils.split(enabledFormats, ",")) + .mapToInt(Integer::parseInt) + .forEach(formats::add); + } catch (NumberFormatException e) { + Log.w(TAG, "ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS misformatted.", e); + } + } + return formats; + } + + private boolean isSurroundFormat(int audioFormat) { + for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) { + if (sf == audioFormat) { + return true; + } + } + return false; + } + private void sendEnabledSurroundFormats(ContentResolver cr, boolean forceUpdate) { if (mEncodedSurroundMode != Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL) { // Manually enable surround formats only when the setting is in manual mode. @@ -1860,14 +1981,7 @@ public class AudioService extends IAudioService.Stub for (String format : surroundFormats) { try { int audioFormat = Integer.valueOf(format); - boolean isSurroundFormat = false; - for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) { - if (sf == audioFormat) { - isSurroundFormat = true; - break; - } - } - if (isSurroundFormat && !formats.contains(audioFormat)) { + if (isSurroundFormat(audioFormat) && !formats.contains(audioFormat)) { formats.add(audioFormat); } } catch (Exception e) { @@ -7343,7 +7457,6 @@ public class AudioService extends IAudioService.Stub Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ENCODED_SURROUND_OUTPUT), false, this); - mEnabledSurroundFormats = Settings.Global.getString( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); mContentResolver.registerContentObserver(Settings.Global.getUriFor( diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 41bc0b928d41..874e9a6deda6 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -1054,27 +1054,16 @@ public class ClipboardService extends SystemService { clipboard.mNotifiedUids.put(uid, true); Binder.withCleanCallingIdentity(() -> { - // Retrieve the app label of the source of the clip data - CharSequence sourceAppLabel = null; - if (clipboard.mPrimaryClipPackage != null) { - try { - sourceAppLabel = mPm.getApplicationLabel(mPm.getApplicationInfoAsUser( - clipboard.mPrimaryClipPackage, 0, userId)); - } catch (PackageManager.NameNotFoundException e) { - // leave label as null - } - } - try { CharSequence callingAppLabel = mPm.getApplicationLabel( mPm.getApplicationInfoAsUser(callingPackage, 0, userId)); String message; - if (sourceAppLabel != null) { + if (isText(clipboard.primaryClip)) { message = getContext().getString( - R.string.pasted_from_app, callingAppLabel, sourceAppLabel); + R.string.pasted_text, callingAppLabel); } else { message = getContext().getString( - R.string.pasted_from_clipboard, callingAppLabel); + R.string.pasted_content, callingAppLabel); } Slog.i(TAG, message); Toast.makeText( @@ -1085,4 +1074,21 @@ public class ClipboardService extends SystemService { } }); } + + /** + * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if + * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and + * no URI or Intent. Note that HTML may be provided along with text so the presence of + * HtmlText in the clip does not prevent this method returning true. + */ + private static boolean isText(@NonNull ClipData data) { + if (data.getItemCount() > 1) { + return false; + } + ClipData.Item item = data.getItemAt(0); + + return !TextUtils.isEmpty(item.getText()) && item.getUri() == null + && item.getIntent() == null; + } + } diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 028cfee36593..9326d692f6e4 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -16,6 +16,7 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; @@ -116,6 +117,33 @@ public class FullScore { } /** + * Given a score supplied by the NetworkAgent, produce a prospective score for an offer. + * + * NetworkOffers have score filters that are compared to the scores of actual networks + * to see if they could possibly beat the current satisfier. Some things the agent can't + * know in advance ; a good example is the validation bit – some networks will validate, + * others won't. For comparison purposes, assume the best, so all possibly beneficial + * networks will be brought up. + * + * @param score the score supplied by the agent for this offer + * @param caps the capabilities supplied by the agent for this offer + * @return a FullScore appropriate for comparing to actual network's scores. + */ + public static FullScore makeProspectiveScore(@NonNull final NetworkScore score, + @NonNull final NetworkCapabilities caps) { + // If the network offers Internet access, it may validate. + final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET); + // VPN transports are known in advance. + final boolean vpn = caps.hasTransport(TRANSPORT_VPN); + // The network hasn't been chosen by the user (yet, at least). + final boolean everUserSelected = false; + // Don't assume the user will accept unvalidated connectivity. + final boolean acceptUnvalidated = false; + return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected, + acceptUnvalidated); + } + + /** * Return a new score given updated caps and config. * * @param caps the NetworkCapabilities of the network diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java new file mode 100644 index 000000000000..548db6b6ddbb --- /dev/null +++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java @@ -0,0 +1,93 @@ +/* + * 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.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.INetworkOfferCallback; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; + +import java.util.Objects; + + +/** + * Represents an offer made by a NetworkProvider to create a network if a need arises. + * + * This class contains the prospective score and capabilities of the network. The provider + * is not obligated to caps able to create a network satisfying this, nor to build a network + * with the exact score and/or capabilities passed ; after all, not all providers know in + * advance what a network will look like after it's connected. Instead, this is meant as a + * filter to limit requests sent to the provider by connectivity to those that this offer stands + * a chance to fulfill. + * + * @see NetworkProvider#offerNetwork. + * + * @hide + */ +public class NetworkOffer { + @NonNull public final FullScore score; + @NonNull public final NetworkCapabilities caps; + @NonNull public final INetworkOfferCallback callback; + @NonNull public final int providerId; + + private static NetworkCapabilities emptyCaps() { + final NetworkCapabilities nc = new NetworkCapabilities(); + return nc; + } + + // Ideally the caps argument would be non-null, but null has historically meant no filter + // and telephony passes null. Keep backward compatibility. + public NetworkOffer(@NonNull final FullScore score, + @Nullable final NetworkCapabilities caps, + @NonNull final INetworkOfferCallback callback, + @NonNull final int providerId) { + this.score = Objects.requireNonNull(score); + this.caps = null != caps ? caps : emptyCaps(); + this.callback = Objects.requireNonNull(callback); + this.providerId = providerId; + } + + /** + * Migrate from, and take over, a previous offer. + * + * When an updated offer is sent from a provider, call this method on the new offer, passing + * the old one, to take over the state. + * + * @param previousOffer + */ + public void migrateFrom(@NonNull final NetworkOffer previousOffer) { + if (!callback.equals(previousOffer.callback)) { + throw new IllegalArgumentException("Can only migrate from a previous version of" + + " the same offer"); + } + } + + /** + * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities. + * @param request The request to test against. + * @return Whether this offer can satisfy the request. + */ + public final boolean canSatisfy(@NonNull final NetworkRequest request) { + return request.networkCapabilities.satisfiedByNetworkCapabilities(caps); + } + + @Override + public String toString() { + return "NetworkOffer [ Score " + score + " ]"; + } +} diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index bd9a52010b96..a070f272fd1d 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1133,6 +1133,8 @@ public class Vpn { * @return a Network if there is a running VPN network or null if there is no running VPN * network or network is null. */ + @VisibleForTesting + @Nullable public synchronized Network getNetwork() { final NetworkAgent agent = mNetworkAgent; if (null == agent) return null; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index c44089b7817f..f173fc7386ba 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -691,6 +691,7 @@ public class ContextHubService extends IContextHubService.Stub { sendLocationSettingUpdate(); sendWifiSettingUpdate(true /* forceUpdate */); sendAirplaneModeSettingUpdate(); + sendMicrophoneDisableSettingUpdateForCurrentUser(); mTransactionManager.onHubReset(); queryNanoAppsInternal(contextHubId); @@ -1123,6 +1124,7 @@ public class ContextHubService extends IContextHubService.Stub { */ public void onUserChanged() { Log.d(TAG, "User changed to id: " + getCurrentUserId()); + sendLocationSettingUpdate(); sendMicrophoneDisableSettingUpdateForCurrentUser(); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 39ed7e8b1e1a..2e4d41c7d364 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -96,9 +96,10 @@ public abstract class NetworkPolicyManagerInternal { /** * Notifies that the specified {@link NetworkStatsProvider} has reached its quota - * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)}. + * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or + * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. * * @param tag the human readable identifier of the custom network stats provider. */ - public abstract void onStatsProviderLimitReached(@NonNull String tag); + public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 16eac91ee6bb..4299d9e81915 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -40,6 +40,14 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED; +import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY; +import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; +import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; +import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; @@ -68,15 +76,7 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLI import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST; import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; -import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_ADMIN_DISABLED; -import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER; import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_MASK; -import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_APP_STANDBY; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_DOZE; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS; @@ -430,7 +430,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17; private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18; private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19; - private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20; + private static final int MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED = 20; // TODO: Add similar docs for other messages. /** * Message to indicate that reasons for why an uid is blocked changed. @@ -2034,39 +2034,34 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED; final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED; - if (hasLimit || policy.metered) { - final long quotaBytes; - if (hasLimit && policy.hasCycle()) { - final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager - .cycleIterator(policy).next(); - final long start = cycle.first.toInstant().toEpochMilli(); - final long end = cycle.second.toInstant().toEpochMilli(); - final long totalBytes = getTotalBytes(policy.template, start, end); - - if (policy.lastLimitSnooze >= start) { - // snoozing past quota, but we still need to restrict apps, - // so push really high quota. - quotaBytes = Long.MAX_VALUE; - } else { - // remaining "quota" bytes are based on total usage in - // current cycle. kernel doesn't like 0-byte rules, so we - // set 1-byte quota and disable the radio later. - quotaBytes = Math.max(1, policy.limitBytes - totalBytes); - } - } else { - // metered network, but no policy limit; we still need to - // restrict apps, so push really high quota. - quotaBytes = Long.MAX_VALUE; + long limitBytes = Long.MAX_VALUE; + if (hasLimit && policy.hasCycle()) { + final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager + .cycleIterator(policy).next(); + final long start = cycle.first.toInstant().toEpochMilli(); + final long end = cycle.second.toInstant().toEpochMilli(); + final long totalBytes = getTotalBytes(policy.template, start, end); + + if (policy.lastLimitSnooze < start) { + // remaining "quota" bytes are based on total usage in + // current cycle. kernel doesn't like 0-byte rules, so we + // set 1-byte quota and disable the radio later. + limitBytes = Math.max(1, policy.limitBytes - totalBytes); } + } + if (hasLimit || policy.metered) { if (matchingIfaces.size() > 1) { // TODO: switch to shared quota once NMS supports Slog.w(TAG, "shared quota unsupported; generating rule for each iface"); } + // Set the interface limit. For interfaces which has no cycle, or metered with + // no policy limit, or snoozed limit notification; we still need to put iptables + // rule hooks to restrict apps for data saver, so push really high quota. for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); - setInterfaceQuotaAsync(iface, quotaBytes); + setInterfaceQuotaAsync(iface, limitBytes); newMeteredIfaces.add(iface); } } @@ -4970,7 +4965,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } - case MSG_STATS_PROVIDER_LIMIT_REACHED: { + case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: { mNetworkStats.forceUpdate(); synchronized (mNetworkPoliciesSecondLock) { @@ -5726,9 +5721,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void onStatsProviderLimitReached(@NonNull String tag) { - Log.v(TAG, "onStatsProviderLimitReached: " + tag); - mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget(); + public void onStatsProviderWarningOrLimitReached(@NonNull String tag) { + Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag); + mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget(); } } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index fe43c311da6f..785e487ed10d 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1676,7 +1676,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void setStatsProviderLimitAsync(@NonNull String iface, long quota) { if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")"); - invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetLimit(iface, quota)); + // TODO: Set warning accordingly. + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, + NetworkStatsProvider.QUOTA_UNLIMITED, quota)); } } @@ -2071,10 +2073,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public void notifyLimitReached() { - Log.d(TAG, mTag + ": onLimitReached"); + public void notifyWarningOrLimitReached() { + Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); LocalServices.getService(NetworkPolicyManagerInternal.class) - .onStatsProviderLimitReached(mTag); + .onStatsProviderWarningOrLimitReached(mTag); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a8bdedf19cc7..764fa0218023 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -778,16 +778,6 @@ public class PackageManagerService extends IPackageManager.Stub private static final String COMPANION_PACKAGE_NAME = "com.android.companiondevicemanager"; - /** Canonical intent used to identify what counts as a "web browser" app */ - private static final Intent sBrowserIntent; - static { - sBrowserIntent = new Intent(); - sBrowserIntent.setAction(Intent.ACTION_VIEW); - sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE); - sBrowserIntent.setData(Uri.parse("http:")); - sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL); - } - // Compilation reasons. public static final int REASON_UNKNOWN = -1; public static final int REASON_FIRST_BOOT = 0; @@ -5636,7 +5626,7 @@ public class PackageManagerService extends IPackageManager.Stub // Work that needs to happen on first install within each user if (firstUserIds != null && firstUserIds.length > 0) { for (int userId : firstUserIds) { - clearRolesAndRestorePermissionsForNewUserInstall(packageName, + restorePermissionsAndUpdateRolesForNewUserInstall(packageName, pkgSetting.getInstallReason(userId), userId); } } @@ -7752,19 +7742,6 @@ public class PackageManagerService extends IPackageManager.Stub return matches.get(0).getComponentInfo().getComponentName(); } - private boolean packageIsBrowser(String packageName, int userId) { - List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null, - PackageManager.MATCH_ALL, userId); - final int N = list.size(); - for (int i = 0; i < N; i++) { - ResolveInfo info = list.get(i); - if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) { - return true; - } - } - return false; - } - @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -15465,16 +15442,55 @@ public class PackageManagerService extends IPackageManager.Stub null); } - private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId, + @VisibleForTesting(visibility = Visibility.PRIVATE) + void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId, boolean suspended) { - final Bundle extras = new Bundle(3); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); - sendPackageBroadcast( - suspended ? Intent.ACTION_PACKAGES_SUSPENDED - : Intent.ACTION_PACKAGES_UNSUSPENDED, - null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, - new int[] {userId}, null, null, null); + final List<List<String>> pkgsToSend = new ArrayList(pkgList.length); + final List<IntArray> uidsToSend = new ArrayList(pkgList.length); + final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length); + final int[] userIds = new int[] {userId}; + // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if + // allow lists are the same. + synchronized (mLock) { + for (int i = 0; i < pkgList.length; i++) { + final String pkgName = pkgList[i]; + final int uid = uidList[i]; + SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList( + getPackageSettingInternal(pkgName, Process.SYSTEM_UID), + userIds, mSettings.getPackagesLocked()); + if (allowList == null) { + allowList = new SparseArray<>(0); + } + boolean merged = false; + for (int j = 0; j < allowListsToSend.size(); j++) { + if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) { + pkgsToSend.get(j).add(pkgName); + uidsToSend.get(j).add(uid); + merged = true; + break; + } + } + if (!merged) { + pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName))); + uidsToSend.add(IntArray.wrap(new int[] {uid})); + allowListsToSend.add(allowList); + } + } + } + + for (int i = 0; i < pkgsToSend.size(); i++) { + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()])); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray()); + final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0 + ? null : allowListsToSend.get(i); + sendPackageBroadcast( + suspended ? Intent.ACTION_PACKAGES_SUSPENDED + : Intent.ACTION_PACKAGES_UNSUSPENDED, + null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, + userIds, null, allowList, null); + } } /** @@ -15618,7 +15634,7 @@ public class PackageManagerService extends IPackageManager.Stub PostInstallData postInstallData = new PostInstallData(null, res, () -> { - clearRolesAndRestorePermissionsForNewUserInstall(packageName, + restorePermissionsAndUpdateRolesForNewUserInstall(packageName, pkgSetting.getInstallReason(userId), userId); if (intentSender != null) { onRestoreComplete(res.returnCode, mContext, intentSender); @@ -21188,7 +21204,6 @@ public class PackageManagerService extends IPackageManager.Stub final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mLock) { mDomainVerificationManager.clearPackage(deletedPs.name); - clearDefaultBrowserIfNeeded(packageName); mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName); mAppsFilter.removePackage(getPackageSetting(packageName)); removedAppId = mSettings.removePackageLPw(packageName); @@ -21746,7 +21761,6 @@ public class PackageManagerService extends IPackageManager.Stub destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); } - clearDefaultBrowserIfNeededForUser(ps.name, nextUserId); removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), nextUserId, ps.appId); clearPackagePreferredActivities(ps.name, nextUserId); mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs, @@ -22263,36 +22277,8 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId); } - /** Clears state for all users, and touches intent filter verification policy */ - void clearDefaultBrowserIfNeeded(String packageName) { - for (int oneUserId : mUserManager.getUserIds()) { - clearDefaultBrowserIfNeededForUser(packageName, oneUserId); - } - } - - private void clearDefaultBrowserIfNeededForUser(String packageName, int userId) { - final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(userId); - if (!TextUtils.isEmpty(defaultBrowserPackageName)) { - if (packageName.equals(defaultBrowserPackageName)) { - mDefaultAppProvider.setDefaultBrowser(null, true, userId); - } - } - } - - private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName, + private void restorePermissionsAndUpdateRolesForNewUserInstall(String packageName, int installReason, @UserIdInt int userId) { - // If this app is a browser and it's newly-installed for some - // users, clear any default-browser state in those users. The - // app's nature doesn't depend on the user, so we can just check - // its browser nature in any user and generalize. - if (packageIsBrowser(packageName, userId)) { - // If this browser is restored from user's backup, do not clear - // default-browser state for this user - if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) { - mDefaultAppProvider.setDefaultBrowser(null, true, userId); - } - } - // We may also need to apply pending (restored) runtime permission grants // within these users. mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId); @@ -22326,11 +22312,6 @@ public class PackageManagerService extends IPackageManager.Stub } } updateDefaultHomeNotLocked(userId); - // TODO: We have to reset the default SMS and Phone. This requires - // significant refactoring to keep all default apps in the package - // manager (cleaner but more work) or have the services provide - // callbacks to the package manager to request a default app reset. - mDefaultAppProvider.setDefaultBrowser(null, true, userId); resetNetworkPolicies(userId); synchronized (mLock) { scheduleWritePackageRestrictionsLocked(userId); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index b5765b50e746..0ddb6cd50d4a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -104,8 +104,8 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata; -import com.android.server.pm.verify.domain.DomainVerificationShell; import com.android.server.pm.permission.LegacyPermissionManagerInternal; +import com.android.server.pm.verify.domain.DomainVerificationShell; import dalvik.system.DexFile; @@ -2251,8 +2251,8 @@ class PackageManagerShellCommand extends ShellCommand { } } - final String packageName = getNextArg(); - if (packageName == null) { + final List<String> packageNames = getRemainingArgs(); + if (packageNames.isEmpty()) { pw.println("Error: package name not specified"); return 1; } @@ -2270,12 +2270,15 @@ class PackageManagerShellCommand extends ShellCommand { try { final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL, "runSuspend"); - mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, - ((appExtras.size() > 0) ? appExtras : null), + mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}), + suspendedState, ((appExtras.size() > 0) ? appExtras : null), ((launcherExtras.size() > 0) ? launcherExtras : null), info, callingPackage, translatedUserId); - pw.println("Package " + packageName + " new suspended state: " - + mInterface.isPackageSuspendedForUser(packageName, translatedUserId)); + for (int i = 0; i < packageNames.size(); i++) { + final String packageName = packageNames.get(i); + pw.println("Package " + packageName + " new suspended state: " + + mInterface.isPackageSuspendedForUser(packageName, translatedUserId)); + } return 0; } catch (RemoteException | IllegalArgumentException e) { pw.println(e.toString()); @@ -3643,11 +3646,11 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); pw.println(""); - pw.println(" suspend [--user USER_ID] TARGET-PACKAGE"); - pw.println(" Suspends the specified package (as user)."); + pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]"); + pw.println(" Suspends the specified package(s) (as user)."); pw.println(""); - pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE"); - pw.println(" Unsuspends the specified package (as user)."); + pw.println(" unsuspend [--user USER_ID] PACKAGE [PACKAGE...]"); + pw.println(" Unsuspends the specified package(s) (as user)."); pw.println(""); pw.println(" grant [--user USER_ID] PACKAGE PERMISSION"); pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION"); diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java new file mode 100644 index 000000000000..054044b47245 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.IBinder; + +/** + * Class needed to expose some {@link ActivityRecord} fields in order to provide + * {@link android.service.voice.VoiceInteractionSession#onHandleAssist(AssistState)} + * + * @hide + */ +public class ActivityAssistInfo { + private final IBinder mActivityToken; + private final IBinder mAssistToken; + private final int mTaskId; + + public ActivityAssistInfo(ActivityRecord activityRecord) { + this.mActivityToken = activityRecord.appToken; + this.mAssistToken = activityRecord.assistToken; + this.mTaskId = activityRecord.getTask().mTaskId; + } + + /** @hide */ + public IBinder getActivityToken() { + return mActivityToken; + } + + /** @hide */ + public IBinder getAssistToken() { + return mAssistToken; + } + + /** @hide */ + public int getTaskId() { + return mTaskId; + } +} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 12c67bb9b7be..c09136eac302 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -32,7 +32,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.service.voice.IVoiceInteractionSession; -import android.util.Pair; import android.util.proto.ProtoOutputStream; import android.window.TaskSnapshot; @@ -166,7 +165,7 @@ public abstract class ActivityTaskManagerInternal { * Returns the top activity from each of the currently visible root tasks, and the related task * id. The first entry will be the focused activity. */ - public abstract List<Pair<IBinder, Integer>> getTopVisibleActivities(); + public abstract List<ActivityAssistInfo> getTopVisibleActivities(); /** * Returns whether {@code uid} has any resumed activity. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 29c5cec59789..c8fa50c35baa 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -215,7 +215,6 @@ import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -5112,7 +5111,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public List<Pair<IBinder, Integer>> getTopVisibleActivities() { + public List<ActivityAssistInfo> getTopVisibleActivities() { synchronized (mGlobalLock) { return mRootWindowContainer.getTopVisibleActivities(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 857217fb19e2..72610481ad32 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1817,8 +1817,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @return a list of pairs, containing activities and their task id which are the top ones in * each visible root task. The first entry will be the focused activity. */ - List<Pair<IBinder, Integer>> getTopVisibleActivities() { - final ArrayList<Pair<IBinder, Integer>> topVisibleActivities = new ArrayList<>(); + List<ActivityAssistInfo> getTopVisibleActivities() { + final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>(); final Task topFocusedRootTask = getTopDisplayFocusedRootTask(); // Traverse all displays. forAllRootTasks(rootTask -> { @@ -1826,8 +1826,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (rootTask.shouldBeVisible(null /* starting */)) { final ActivityRecord top = rootTask.getTopNonFinishingActivity(); if (top != null) { - Pair<IBinder, Integer> visibleActivity = new Pair<>(top.appToken, - top.getTask().mTaskId); + ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top); if (rootTask == topFocusedRootTask) { topVisibleActivities.add(0, visibleActivity); } else { diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index a65adbbf5044..8b08314136d4 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.view.View; import android.view.WindowManager.LayoutParams.WindowType; +import android.window.WindowContext; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -40,22 +41,21 @@ import com.android.internal.protolog.common.ProtoLog; import java.util.Objects; /** - * A controller to register/unregister {@link WindowContainerListener} for - * {@link android.app.WindowContext}. + * A controller to register/unregister {@link WindowContainerListener} for {@link WindowContext}. * * <ul> - * <li>When a {@link android.app.WindowContext} is created, it registers the listener via + * <li>When a {@link WindowContext} is created, it registers the listener via * {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)} * automatically.</li> - * <li>When the {@link android.app.WindowContext} adds the first window to the screen via + * <li>When the {@link WindowContext} adds the first window to the screen via * {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)}, * {@link WindowManagerService} then updates the {@link WindowContextListenerImpl} to listen * to corresponding {@link WindowToken} via this controller.</li> - * <li>When the {@link android.app.WindowContext} is GCed, it unregisters the previously + * <li>When the {@link WindowContext} is GCed, it unregisters the previously * registered listener via * {@link WindowManagerService#unregisterWindowContextListener(IBinder)}. * {@link WindowManagerService} is also responsible for removing the - * {@link android.app.WindowContext} created {@link WindowToken}.</li> + * {@link WindowContext} created {@link WindowToken}.</li> * </ul> * <p>Note that the listener may be removed earlier than the * {@link #unregisterWindowContainerListener(IBinder)} if the listened {@link WindowContainer} was diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c99b01f1d2b5..a8ca5b6d6f37 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2708,7 +2708,7 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration + * Registers a listener for a {@link android.window.WindowContext} to subscribe to configuration * changes of a {@link DisplayArea}. * * @param clientToken the window context's token diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5163a431bb84..d54cf5f17b4a 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -54,6 +54,7 @@ import android.view.DisplayInfo; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowManager; +import android.window.WindowContext; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -109,7 +110,7 @@ class WindowToken extends WindowContainer<WindowState> { private FixedRotationTransformState mFixedRotationTransformState; /** - * When set to {@code true}, this window token is created from {@link android.app.WindowContext} + * When set to {@code true}, this window token is created from {@link WindowContext} */ private final boolean mFromClientToken; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 09ae8fc4e03a..44791b00d870 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10599,21 +10599,58 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + /** + * Returns the apps that are non-exempt from some policies (such as suspension), and populates + * the given set with the apps that are exempt. + * + * @param packageNames apps to check + * @param outputExemptApps will be populate with subset of {@code packageNames} that is exempt + * from some policy restrictions + * + * @return subset of {@code packageNames} that is affected by some policy restrictions. + */ + private String[] populateNonExemptAndExemptFromPolicyApps(String[] packageNames, + Set<String> outputExemptApps) { + Preconditions.checkArgument(outputExemptApps.isEmpty(), "outputExemptApps is not empty"); + List<String> exemptApps = listPolicyExemptAppsUnchecked(); + if (exemptApps.isEmpty()) { + return packageNames; + } + List<String> nonExemptApps = new ArrayList<>(packageNames.length); + for (int i = 0; i < packageNames.length; i++) { + String app = packageNames[i]; + if (exemptApps.contains(app)) { + outputExemptApps.add(app); + } else { + nonExemptApps.add(app); + } + } + String[] result = new String[nonExemptApps.size()]; + nonExemptApps.toArray(result); + return result; + } + @Override public String[] setPackagesSuspended(ComponentName who, String callerPackage, String[] packageNames, boolean suspended) { + Objects.requireNonNull(packageNames, "array of packages cannot be null"); final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED); - String[] result = null; + // Must remove the exempt apps from the input before calling PM, then add them back to + // the array returned to the caller + Set<String> exemptApps = new HashSet<>(); + packageNames = populateNonExemptAndExemptFromPolicyApps(packageNames, exemptApps); + + String[] nonSuspendedPackages = null; synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { - result = mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended, null, - null, null, PLATFORM_PACKAGE_NAME, caller.getUserId()); + nonSuspendedPackages = mIPackageManager.setPackagesSuspendedAsUser(packageNames, + suspended, null, null, null, PLATFORM_PACKAGE_NAME, caller.getUserId()); } catch (RemoteException re) { // Shouldn't happen. Slog.e(LOG_TAG, "Failed talking to the package manager", re); @@ -10627,10 +10664,35 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setBoolean(/* isDelegate */ who == null) .setStrings(packageNames) .write(); - if (result != null) { - return result; + + if (nonSuspendedPackages == null) { + Slog.w(LOG_TAG, "PM failed to suspend packages (%s)", Arrays.toString(packageNames)); + return packageNames; } - return packageNames; + if (exemptApps.isEmpty()) { + return nonSuspendedPackages; + } + + String[] result = buildNonSuspendedPackagesUnionArray(nonSuspendedPackages, exemptApps); + if (VERBOSE_LOG) Slog.v(LOG_TAG, "Returning %s", Arrays.toString(result)); + return result; + } + + /** + * Returns an array containing the union of the given non-suspended packages and + * exempt apps. Assumes both parameters are non-null and non-empty. + */ + private String[] buildNonSuspendedPackagesUnionArray(String[] nonSuspendedPackages, + Set<String> exemptApps) { + String[] result = new String[nonSuspendedPackages.length + exemptApps.size()]; + int index = 0; + for (String app : nonSuspendedPackages) { + result[index++] = app; + } + for (String app : exemptApps) { + result[index++] = app; + } + return result; } @Override @@ -10656,9 +10718,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<String> listPolicyExemptApps() { + CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller) + || isProfileOwner(caller)); + + return listPolicyExemptAppsUnchecked(); + } + private List<String> listPolicyExemptAppsUnchecked() { // TODO(b/181238156): decide whether it should only list the apps set by the resources, // or also the "critical" apps defined by PersonalAppsSuspensionHelper (like SMS app). // If it's the latter, refactor PersonalAppsSuspensionHelper so it (or a superclass) takes diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index bd3f99a5efb8..94f8e59c6057 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -100,6 +100,21 @@ static bool isPageAligned(IncFsSize s) { return (s & (Constants::blockSize - 1)) == 0; } +static bool getEnforceReadLogsMaxIntervalForSystemDataLoaders() { + return android::base::GetBoolProperty("debug.incremental.enforce_readlogs_max_interval_for_" + "system_dataloaders", + false); +} + +static Seconds getReadLogsMaxInterval() { + constexpr int limit = duration_cast<Seconds>(Constants::readLogsMaxInterval).count(); + int readlogs_max_interval_secs = + std::min(limit, + android::base::GetIntProperty< + int>("debug.incremental.readlogs_max_interval_sec", limit)); + return Seconds{readlogs_max_interval_secs}; +} + template <base::LogSeverity level = base::ERROR> bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) { auto cstr = path::c_str(name); @@ -308,6 +323,14 @@ void IncrementalService::IncFsMount::setReadLogsEnabled(bool value) { } } +void IncrementalService::IncFsMount::setReadLogsRequested(bool value) { + if (value) { + flags |= StorageFlags::ReadLogsRequested; + } else { + flags &= ~StorageFlags::ReadLogsRequested; + } +} + IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir) : mVold(sm.getVoldService()), mDataLoaderManager(sm.getDataLoaderManager()), @@ -711,7 +734,8 @@ bool IncrementalService::startLoading(StorageId storageId, dataLoaderStub = ifs->dataLoaderStub; } - if (dataLoaderStub->isSystemDataLoader()) { + if (dataLoaderStub->isSystemDataLoader() && + !getEnforceReadLogsMaxIntervalForSystemDataLoaders()) { // Readlogs from system dataloader (adb) can always be collected. ifs->startLoadingTs = TimePoint::max(); } else { @@ -719,7 +743,7 @@ bool IncrementalService::startLoading(StorageId storageId, const auto startLoadingTs = mClock->now(); ifs->startLoadingTs = startLoadingTs; // Setup a callback to disable the readlogs after max interval. - addTimedJob(*mTimedQueue, storageId, Constants::readLogsMaxInterval, + addTimedJob(*mTimedQueue, storageId, getReadLogsMaxInterval(), [this, storageId, startLoadingTs]() { const auto ifs = getIfs(storageId); if (!ifs) { @@ -788,32 +812,38 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog return -EINVAL; } - std::unique_lock l(ifs->lock); - if (!enableReadLogs) { - return disableReadLogsLocked(*ifs); - } + std::string packageName; - if (!ifs->readLogsAllowed()) { - LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId; - return -EPERM; - } + { + std::unique_lock l(ifs->lock); + if (!enableReadLogs) { + return disableReadLogsLocked(*ifs); + } - if (!ifs->dataLoaderStub) { - // This should never happen - only DL can call enableReadLogs. - LOG(ERROR) << "enableReadLogs failed: invalid state"; - return -EPERM; - } + if (!ifs->readLogsAllowed()) { + LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId; + return -EPERM; + } - // Check installation time. - const auto now = mClock->now(); - const auto startLoadingTs = ifs->startLoadingTs; - if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) { - LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: " - << storageId; - return -EPERM; - } + if (!ifs->dataLoaderStub) { + // This should never happen - only DL can call enableReadLogs. + LOG(ERROR) << "enableReadLogs failed: invalid state"; + return -EPERM; + } - const auto& packageName = ifs->dataLoaderStub->params().packageName; + // Check installation time. + const auto now = mClock->now(); + const auto startLoadingTs = ifs->startLoadingTs; + if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) { + LOG(ERROR) + << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: " + << storageId; + return -EPERM; + } + + packageName = ifs->dataLoaderStub->params().packageName; + ifs->setReadLogsRequested(true); + } // Check loader usage stats permission and apop. if (auto status = @@ -833,8 +863,14 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog return fromBinderStatus(status); } - if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) { - return status; + { + std::unique_lock l(ifs->lock); + if (!ifs->readLogsRequested()) { + return 0; + } + if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) { + return status; + } } registerAppOpsCallback(packageName); @@ -843,6 +879,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog } int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) { + ifs.setReadLogsRequested(false); return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false); } @@ -1049,17 +1086,14 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m return err; } if (params.size > 0) { - // Only v2+ incfs supports automatically trimming file over-reserved sizes - if (mIncFs->features() & incfs::Features::v2) { - if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) { - if (err != -EOPNOTSUPP) { - LOG(ERROR) << "Failed to reserve space for a new file: " << err; - (void)mIncFs->unlink(ifs->control, normPath); - return err; - } else { - LOG(WARNING) << "Reserving space for backing file isn't supported, " - "may run out of disk later"; - } + if (auto err = mIncFs->reserveSpace(ifs->control, id, params.size)) { + if (err != -EOPNOTSUPP) { + LOG(ERROR) << "Failed to reserve space for a new file: " << err; + (void)mIncFs->unlink(ifs->control, normPath); + return err; + } else { + LOG(WARNING) << "Reserving space for backing file isn't supported, " + "may run out of disk later"; } } if (!data.empty()) { @@ -1643,6 +1677,15 @@ void IncrementalService::runCmdLooper() { } } +void IncrementalService::trimReservedSpaceV1(const IncFsMount& ifs) { + mIncFs->forEachFile(ifs.control, [this](auto&& control, auto&& fileId) { + if (mIncFs->isFileFullyLoaded(control, fileId) == incfs::LoadingState::Full) { + mIncFs->reserveSpace(control, fileId, -1); + } + return true; + }); +} + void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener, const StorageHealthCheckParams& healthCheckParams, @@ -1662,6 +1705,22 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara std::move(statusListener), healthCheckParams, std::move(healthListener), path::join(ifs.root, constants().mount)); + // pre-v2 IncFS doesn't do automatic reserved space trimming - need to run it manually + if (!(mIncFs->features() & incfs::Features::v2)) { + addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool { + if (!state.fullyLoaded) { + return true; + } + + const auto ifs = getIfs(storageId); + if (!ifs) { + return false; + } + trimReservedSpaceV1(*ifs); + return false; + }); + } + addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool { if (!state.fullyLoaded || state.readLogsEnabled) { return true; @@ -2198,7 +2257,6 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { affected.reserve(mMounts.size()); for (auto&& [id, ifs] : mMounts) { std::unique_lock ll(ifs->lock); - if (ifs->mountId == id && ifs->dataLoaderStub && ifs->dataLoaderStub->params().packageName == packageName) { affected.push_back(ifs); @@ -2206,7 +2264,8 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { } } for (auto&& ifs : affected) { - applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false); + std::unique_lock ll(ifs->lock); + disableReadLogsLocked(*ifs); } } @@ -2235,7 +2294,7 @@ void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallba mIfsStateCallbacks[storageId].emplace_back(std::move(callback)); } if (wasEmpty) { - addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval, + addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval, [this]() { processIfsStateCallbacks(); }); } } @@ -2251,29 +2310,36 @@ void IncrementalService::processIfsStateCallbacks() { } IfsStateCallbacks::iterator it; if (storageId == kInvalidStorageId) { - // First entry, initialize the it. + // First entry, initialize the |it|. it = mIfsStateCallbacks.begin(); } else { - // Subsequent entries, update the storageId, and shift to the new one. - it = mIfsStateCallbacks.find(storageId); + // Subsequent entries, update the |storageId|, and shift to the new one (not that + // it guarantees much about updated items, but at least the loop will finish). + it = mIfsStateCallbacks.lower_bound(storageId); if (it == mIfsStateCallbacks.end()) { - // Was removed while processing, too bad. + // Nothing else left, too bad. break; } - - auto& callbacks = it->second; - if (callbacks.empty()) { - std::swap(callbacks, local); + if (it->first != storageId) { + local.clear(); // Was removed during processing, forget the old callbacks. } else { - callbacks.insert(callbacks.end(), local.begin(), local.end()); - } - if (callbacks.empty()) { - it = mIfsStateCallbacks.erase(it); - if (mIfsStateCallbacks.empty()) { - return; + // Put the 'surviving' callbacks back into the map and advance the position. + auto& callbacks = it->second; + if (callbacks.empty()) { + std::swap(callbacks, local); + } else { + callbacks.insert(callbacks.end(), std::move_iterator(local.begin()), + std::move_iterator(local.end())); + local.clear(); + } + if (callbacks.empty()) { + it = mIfsStateCallbacks.erase(it); + if (mIfsStateCallbacks.empty()) { + return; + } + } else { + ++it; } - } else { - ++it; } } @@ -2293,7 +2359,7 @@ void IncrementalService::processIfsStateCallbacks() { processIfsStateCallbacks(storageId, local); } - addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval, + addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval, [this]() { processIfsStateCallbacks(); }); } diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index a8f32dec824e..fb6f56c9166e 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -95,7 +95,8 @@ public: #pragma GCC diagnostic pop static constexpr StorageId kInvalidStorageId = -1; - static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max(); + static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max() - 1; + static constexpr StorageId kAllStoragesId = kMaxStorageId + 1; static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max(); @@ -116,6 +117,7 @@ public: enum StorageFlags { ReadLogsAllowed = 1 << 0, ReadLogsEnabled = 1 << 1, + ReadLogsRequested = 1 << 2, }; struct LoadingProgress { @@ -365,6 +367,9 @@ private: void setReadLogsEnabled(bool value); int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); } + void setReadLogsRequested(bool value); + int32_t readLogsRequested() const { return (flags & StorageFlags::ReadLogsRequested); } + static void cleanupFilesystem(std::string_view root); }; @@ -447,6 +452,8 @@ private: StorageLoadingProgressListener&& progressListener); long getMillsSinceOldestPendingRead(StorageId storage); + void trimReservedSpaceV1(const IncFsMount& ifs); + private: const std::unique_ptr<VoldServiceWrapper> mVold; const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager; @@ -468,7 +475,7 @@ private: std::mutex mCallbacksLock; std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered; - using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>; + using IfsStateCallbacks = std::map<StorageId, std::vector<IfsStateCallback>>; std::mutex mIfsStateCallbacksLock; IfsStateCallbacks mIfsStateCallbacks; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 34654994c9fc..8e416f36f49e 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -212,6 +212,9 @@ public: std::string_view path) const final { return incfs::isFullyLoaded(control, path); } + incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final { + return incfs::isFullyLoaded(control, id); + } incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final { return incfs::isEverythingFullyLoaded(control); } @@ -227,9 +230,8 @@ public: ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final { return incfs::writeBlocks({blocks.data(), size_t(blocks.size())}); } - ErrorCode reserveSpace(const Control& control, std::string_view path, - IncFsSize size) const final { - return incfs::reserveSpace(control, path, size); + ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final { + return incfs::reserveSpace(control, id, size); } WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { @@ -238,19 +240,26 @@ public: ErrorCode setUidReadTimeouts(const Control& control, const std::vector<android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts) const final { - std::vector<incfs::UidReadTimeouts> timeouts; - timeouts.resize(perUidReadTimeouts.size()); + std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size()); for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) { - auto&& timeout = timeouts[i]; + auto& timeout = timeouts[i]; const auto& perUidTimeout = perUidReadTimeouts[i]; timeout.uid = perUidTimeout.uid; timeout.minTimeUs = perUidTimeout.minTimeUs; timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs; timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs; } - return incfs::setUidReadTimeouts(control, timeouts); } + ErrorCode forEachFile(const Control& control, FileCallback cb) const final { + return incfs::forEachFile(control, + [&](auto& control, FileId id) { return cb(control, id); }); + } + ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final { + return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) { + return cb(control, id); + }); + } }; static JNIEnv* getOrAttachJniEnv(JavaVM* jvm); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index a787db573dfc..d4cdcbe9cac0 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -84,6 +84,8 @@ public: void(std::string_view root, std::string_view backingDir, std::span<std::pair<std::string_view, std::string_view>> binds)>; + using FileCallback = android::base::function_ref<bool(const Control& control, FileId fileId)>; + static std::string toString(FileId fileId); virtual ~IncFsWrapper() = default; @@ -105,14 +107,14 @@ public: const Control& control, std::string_view path) const = 0; virtual incfs::LoadingState isFileFullyLoaded(const Control& control, std::string_view path) const = 0; + virtual incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const = 0; virtual incfs::LoadingState isEverythingFullyLoaded(const Control& control) const = 0; virtual ErrorCode link(const Control& control, std::string_view from, std::string_view to) const = 0; virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; - virtual ErrorCode reserveSpace(const Control& control, std::string_view path, - IncFsSize size) const = 0; + virtual ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const = 0; virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; @@ -120,6 +122,8 @@ public: const Control& control, const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts) const = 0; + virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0; + virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0; }; class AppOpsManagerWrapper { diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index e6d487255eff..ddb778462df5 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -379,6 +379,7 @@ public: std::string_view path)); MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, std::string_view path)); + MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, FileId id)); MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control)); MOCK_CONST_METHOD3(link, ErrorCode(const Control& control, std::string_view from, @@ -386,14 +387,15 @@ public: MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); - MOCK_CONST_METHOD3(reserveSpace, - ErrorCode(const Control& control, std::string_view path, IncFsSize size)); + MOCK_CONST_METHOD3(reserveSpace, ErrorCode(const Control& control, FileId id, IncFsSize size)); MOCK_CONST_METHOD3(waitForPendingReads, WaitResult(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer)); MOCK_CONST_METHOD2(setUidReadTimeouts, ErrorCode(const Control& control, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts)); + MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb)); + MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb)); MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); @@ -1594,7 +1596,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedNoData) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState::MissingBlocks)); ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0); @@ -1605,7 +1607,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedError) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState(-1))); ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0); @@ -1616,7 +1618,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState::Full)); ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); @@ -1735,10 +1737,10 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) { ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED); // IfsState callback present. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval); auto callback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Not loaded yet. EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)) @@ -1751,10 +1753,10 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) { ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED); // Still present. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval); callback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Fully loaded. EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full)); @@ -1797,10 +1799,10 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED); // IfsState callback present. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval); auto callback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Not loaded yet. EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)) @@ -1813,10 +1815,10 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED); // Still present. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval); callback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Fully loaded. EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)) @@ -1832,10 +1834,10 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED); // Still present. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval); callback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Disable readlogs and expect the unbind. EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); @@ -2007,10 +2009,10 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // Timed callback present -> 0 progress. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); const auto timedCallback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Call it again. timedCallback(); @@ -2018,10 +2020,10 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // Still present -> some progress. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); const auto timedCallback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Fully loaded but readlogs collection enabled. ASSERT_GE(mDataLoader->setStorageParams(true), 0); @@ -2032,10 +2034,10 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // Still present -> fully loaded + readlogs. - ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId); + ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId); ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); const auto timedCallback = mTimedQueue->mWhat; - mTimedQueue->clearJob(IncrementalService::kMaxStorageId); + mTimedQueue->clearJob(IncrementalService::kAllStoragesId); // Now disable readlogs. ASSERT_GE(mDataLoader->setStorageParams(false), 0); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt new file mode 100644 index 000000000000..7a6110bdbda3 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.util.SparseArray +import com.android.server.testutils.any +import com.android.server.testutils.eq +import com.android.server.testutils.nullable +import com.android.server.testutils.whenever +import com.android.server.utils.WatchedArrayMap +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +class SuspendPackagesBroadcastTest { + + companion object { + const val TEST_PACKAGE_1 = "com.android.test.package1" + const val TEST_PACKAGE_2 = "com.android.test.package2" + const val TEST_USER_ID = 0 + } + + lateinit var pms: PackageManagerService + lateinit var packageSetting1: PackageSetting + lateinit var packageSetting2: PackageSetting + lateinit var packagesToSuspend: Array<String> + lateinit var uidsToSuspend: IntArray + + @Captor + lateinit var bundleCaptor: ArgumentCaptor<Bundle> + + @Rule + @JvmField + val rule = MockSystemRule() + + @Before + @Throws(Exception::class) + fun setup() { + MockitoAnnotations.initMocks(this) + rule.system().stageNominalSystemState() + pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2)) + packageSetting1 = pms.getPackageSetting(TEST_PACKAGE_1)!! + packageSetting2 = pms.getPackageSetting(TEST_PACKAGE_2)!! + packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10003)) + + pms.sendPackagesSuspendedForUser( + packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true) + verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) + + var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids).asList().containsExactly( + packageSetting1.appId, packageSetting2.appId) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10007)) + + pms.sendPackagesSuspendedForUser( + packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true) + verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, null) + + pms.sendPackagesSuspendedForUser( + packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true) + verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply { + this.put(TEST_USER_ID, uids) + } + + private fun mockAllowList(pkgSetting: PackageSetting, list: SparseArray<IntArray>?) { + whenever(rule.mocks().injector.appsFilter.getVisibilityAllowList(eq(pkgSetting), + any(IntArray::class.java), any() as WatchedArrayMap<String, PackageSetting>)) + .thenReturn(list) + } + + private fun createPackageManagerService(vararg stageExistingPackages: String): + PackageManagerService { + stageExistingPackages.forEach { + rule.system().stageScanExistingPackage(it, 1L, + rule.system().dataAppDirectory) + } + var pms = PackageManagerService(rule.mocks().injector, + false /*coreOnly*/, + false /*factoryTest*/, + MockSystem.DEFAULT_VERSION_INFO.fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL) + rule.system().validateFinalState() + return pms + } +} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java index ad22cbaef575..f3ee2332906f 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java @@ -87,7 +87,8 @@ public class AppSearchImplPlatformTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -98,7 +99,8 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = @@ -139,7 +141,8 @@ public class AppSearchImplPlatformTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -150,7 +153,8 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = @@ -208,7 +212,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // "schema1" is platform hidden now and package visible to package1 assertThat( @@ -234,7 +239,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Check that "schema1" still has the same visibility settings assertThat( @@ -283,7 +289,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // "schema1" is platform hidden now and package accessible assertThat( @@ -305,7 +312,8 @@ public class AppSearchImplPlatformTest { /*schemas=*/ Collections.emptyList(), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*schemaVersion=*/ 0); // Check that "schema1" is no longer considered platform hidden or package accessible assertThat( @@ -328,7 +336,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -351,7 +360,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -369,7 +379,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -387,7 +398,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -416,7 +428,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "Schema", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() 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 e0cdeddc200e..c34c00d62f7e 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 @@ -26,6 +26,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.util.ArrayMap; @@ -406,7 +407,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert enough documents. for (int i = 0; @@ -471,7 +473,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert document GenericDocument document = @@ -504,14 +507,16 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); mAppSearchImpl.setSchema( "package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert documents GenericDocument document1 = @@ -555,7 +560,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert document GenericDocument document = @@ -597,7 +603,8 @@ public class AppSearchImplTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -608,7 +615,8 @@ public class AppSearchImplTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package1 document GenericDocument document = @@ -647,7 +655,8 @@ public class AppSearchImplTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -658,7 +667,8 @@ public class AppSearchImplTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package1 document GenericDocument document = @@ -735,7 +745,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -781,7 +792,8 @@ public class AppSearchImplTest { oldSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create incompatible schema List<AppSearchSchema> newSchemas = @@ -795,7 +807,8 @@ public class AppSearchImplTest { newSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text"); assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email"); } @@ -816,7 +829,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -847,7 +861,8 @@ public class AppSearchImplTest { finalSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Check the Document type has been deleted. assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document"); @@ -859,7 +874,8 @@ public class AppSearchImplTest { finalSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); // Check Document schema is removed. expectedProto = @@ -895,14 +911,16 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); mAppSearchImpl.setSchema( "package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -940,7 +958,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); // Create expected schemaType list, database 1 should only contain Email but database 2 // remains in same. @@ -982,7 +1001,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -994,7 +1014,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -1006,7 +1027,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); } @@ -1024,7 +1046,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); // Has both databases @@ -1035,7 +1058,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); } @@ -1077,6 +1101,330 @@ public class AppSearchImplTest { } @Test + public void testReportUsage() throws Exception { + // 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, + /*version=*/ 0); + + // Insert two docs + GenericDocument document1 = + new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + GenericDocument document2 = + new GenericDocument.Builder<>("namespace", "uri2", "type").build(); + mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null); + mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null); + + // Report some usages. uri1 has 2 app and 1 system usage, uri2 has 1 app and 2 system usage. + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 10, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 20, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 1000, + /*systemUsage=*/ true); + + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 100, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 200, + /*systemUsage=*/ true); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 150, + /*systemUsage=*/ true); + + // Sort by app usage count: uri1 should win + List<SearchResult> page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2"); + + // Sort by app usage timestamp: uri2 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec + .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1"); + + // Sort by system usage count: uri2 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1"); + + // Sort by system usage timestamp: uri1 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec + .RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2"); + } + + @Test + public void testGetStorageInfoForPackage_nonexistentPackage() throws Exception { + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("nonexistent.package"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForPackage_withoutDocument() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Since "package1" doesn't have a document, it get any space attributed to it. + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForPackage_proportionalToDocuments() throws Exception { + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + + // Insert schema for "package1" + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Insert document for "package1" + GenericDocument document = + new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database", document, /*logger=*/ null); + + // Insert schema for "package2" + mAppSearchImpl.setSchema( + "package2", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Insert two documents for "package2" + document = new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null); + document = new GenericDocument.Builder<>("namespace", "uri2", "type").build(); + mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null); + + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); + long size1 = storageInfo.getSizeBytes(); + assertThat(size1).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + storageInfo = mAppSearchImpl.getStorageInfoForPackage("package2"); + long size2 = storageInfo.getSizeBytes(); + assertThat(size2).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + // Size is proportional to number of documents. Since "package2" has twice as many + // documents as "package1", its size is twice as much too. + assertThat(size2).isAtLeast(2 * size1); + } + + @Test + public void testGetStorageInfoForDatabase_nonexistentPackage() throws Exception { + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = + mAppSearchImpl.getStorageInfoForDatabase( + "nonexistent.package", "nonexistentDatabase"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_nonexistentDatabase() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = + mAppSearchImpl.getStorageInfoForDatabase("package1", "nonexistentDatabase"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_withoutDocument() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Since "package1", "database1" doesn't have a document, it get any space attributed to it. + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_proportionalToDocuments() throws Exception { + // Insert schema for "package1", "database1" and "database2" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + mAppSearchImpl.setSchema( + "package1", + "database2", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Add a document for "package1", "database1" + GenericDocument document = + new GenericDocument.Builder<>("namespace1", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null); + + // Add two documents for "package1", "database2" + document = new GenericDocument.Builder<>("namespace1", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null); + document = new GenericDocument.Builder<>("namespace1", "uri2", "type").build(); + mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null); + + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); + long size1 = storageInfo.getSizeBytes(); + assertThat(size1).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database2"); + long size2 = storageInfo.getSizeBytes(); + assertThat(size2).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + // Size is proportional to number of documents. Since "database2" has twice as many + // documents as "database1", its size is twice as much too. + assertThat(size2).isAtLeast(2 * size1); + } + + @Test public void testThrowsExceptionIfClosed() throws Exception { Context context = ApplicationProvider.getApplicationContext(); AppSearchImpl appSearchImpl = @@ -1095,7 +1443,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); appSearchImpl.close(); @@ -1109,7 +1458,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); }); expectThrows( @@ -1179,7 +1529,8 @@ public class AppSearchImplTest { "database", "namespace", "uri", - /*usageTimestampMillis=*/ 1000L); + /*usageTimestampMillis=*/ 1000L, + /*systemUsage=*/ false); }); expectThrows( @@ -1203,6 +1554,18 @@ public class AppSearchImplTest { expectThrows( IllegalStateException.class, () -> { + appSearchImpl.getStorageInfoForPackage("package"); + }); + + expectThrows( + IllegalStateException.class, + () -> { + appSearchImpl.getStorageInfoForDatabase("package", "database"); + }); + + expectThrows( + IllegalStateException.class, + () -> { appSearchImpl.persistToDisk(); }); } diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java index 467ede4efc1c..673b7ee24eff 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java @@ -127,7 +127,8 @@ public class AppSearchLoggerTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); GenericDocument document = new GenericDocument.Builder<>("namespace", "uri", "type").build(); 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 dc225f11ae80..0fe3903ec683 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,7 +32,6 @@ public class SchemaToProtoConverterTest { public void testGetProto_Email() { AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email") - .setVersion(12345) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder("subject") .setCardinality( @@ -89,7 +88,7 @@ public class SchemaToProtoConverterTest { TermMatchType.Code.PREFIX))) .build(); - assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema)) + assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema, /*version=*/ 12345)) .isEqualTo(expectedEmailProto); assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedEmailProto)) .isEqualTo(emailSchema); @@ -142,7 +141,9 @@ public class SchemaToProtoConverterTest { PropertyConfigProto.Cardinality.Code.OPTIONAL)) .build(); - assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(musicRecordingSchema)) + assertThat( + SchemaToProtoConverter.toSchemaTypeConfigProto( + musicRecordingSchema, /*version=*/ 0)) .isEqualTo(expectedMusicRecordingProto); assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedMusicRecordingProto)) .isEqualTo(musicRecordingSchema); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java deleted file mode 100644 index 70718f765412..000000000000 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.hdmi; - -import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; - -import static com.google.common.truth.Truth.assertThat; - -import static junit.framework.Assert.assertEquals; - -import android.content.Context; -import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.HdmiPortInfo; -import android.hardware.hdmi.IHdmiControlCallback; -import android.os.Looper; -import android.os.test.TestLooper; -import android.provider.Settings; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Tests for {@link HdmiControlServiceBinderAPITest} class. - */ -@SmallTest -@RunWith(JUnit4.class) -public class HdmiControlServiceBinderAPITest { - - private Context mContext; - - private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { - - private boolean mCanGoToStandby; - private boolean mIsStandby; - private boolean mIsDisabled; - - protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) { - super(service, deviceType); - } - - @Override - protected void onAddressAllocated(int logicalAddress, int reason) { - } - - @Override - protected int getPreferredAddress() { - return 0; - } - - @Override - protected void setPreferredAddress(int addr) { - } - - @Override - protected boolean canGoToStandby() { - return mCanGoToStandby; - } - - @Override - protected void disableDevice( - boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { - mIsDisabled = true; - originalCallback.onCleared(this); - } - - @Override - protected void onStandby(boolean initiatedByCec, int standbyAction) { - mIsStandby = true; - } - - protected boolean isStandby() { - return mIsStandby; - } - - protected boolean isDisabled() { - return mIsDisabled; - } - - protected void setCanGoToStandby(boolean canGoToStandby) { - mCanGoToStandby = canGoToStandby; - } - - @Override - protected int getRcProfile() { - return 0; - } - - @Override - protected List<Integer> getRcFeatures() { - return Collections.emptyList(); - } - - @Override - protected List<Integer> getDeviceFeatures() { - return Collections.emptyList(); - } - } - - private static final String TAG = "HdmiControlServiceBinderAPITest"; - private HdmiControlService mHdmiControlService; - private HdmiCecController mHdmiCecController; - private HdmiCecLocalDevicePlayback mPlaybackDevice; - private FakeNativeWrapper mNativeWrapper; - private Looper mMyLooper; - private TestLooper mTestLooper = new TestLooper(); - private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); - private HdmiPortInfo[] mHdmiPortInfo; - private int mResult; - private int mPowerStatus; - - @Before - public void SetUp() { - mContext = InstrumentationRegistry.getTargetContext(); - // 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 - void sendCecCommand(HdmiCecMessage command) { - switch (command.getOpcode()) { - case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: - HdmiCecMessage message = - HdmiCecMessageBuilder.buildReportPowerStatus( - Constants.ADDR_TV, - Constants.ADDR_PLAYBACK_1, - HdmiControlManager.POWER_STATUS_ON); - handleCecCommand(message); - break; - default: - return; - } - } - - @Override - boolean isPowerStandby() { - return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; - } - - @Override - protected HdmiCecConfig getHdmiCecConfig() { - return hdmiCecConfig; - } - }; - mMyLooper = mTestLooper.getLooper(); - - mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) { - @Override - protected void wakeUpIfActiveSource() {} - - @Override - protected void setPreferredAddress(int addr) {} - - @Override - protected int getPreferredAddress() { - return Constants.ADDR_PLAYBACK_1; - } - }; - mPlaybackDevice.init(); - - mHdmiControlService.setIoLooper(mMyLooper); - - mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); - mHdmiControlService.setCecController(mHdmiCecController); - mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - - mLocalDevices.add(mPlaybackDevice); - mHdmiPortInfo = new HdmiPortInfo[1]; - mHdmiPortInfo[0] = - new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); - mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initService(); - mResult = -1; - mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - - mTestLooper.dispatchAll(); - } - - @Test - public void oneTouchPlay_addressNotAllocated() { - assertThat(mHdmiControlService.isAddressAllocated()).isFalse(); - mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { - @Override - public void onComplete(int result) { - mResult = result; - } - }); - assertEquals(mResult, -1); - assertThat(mPlaybackDevice.isActiveSource()).isFalse(); - - setHdmiControlEnabled(true); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); - mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); - assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); - assertThat(mPlaybackDevice.isActiveSource()).isTrue(); - } - - @Test - public void oneTouchPlay_addressAllocated() { - setHdmiControlEnabled(true); - - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); - mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); - mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { - @Override - public void onComplete(int result) { - mResult = result; - } - }); - assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); - assertThat(mPlaybackDevice.isActiveSource()).isTrue(); - } - - private void setHdmiControlEnabled(boolean enabled) { - int value = enabled ? 1 : 0; - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED, - value); - } -} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index c61635cbd4b6..d74bff2837ce 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -37,13 +37,13 @@ import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; import android.os.test.TestLooper; +import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -79,12 +79,18 @@ public class OneTouchPlayActionTest { @Mock private IThermalService mIThermalServiceMock; - @Before - public void setUp() throws Exception { + /** + * Manually called before tests, because some tests require HDMI control to be disabled. + * @param hdmiControlEnabled whether to enable the global setting hdmi_control. + * @throws Exception + */ + public void setUp(boolean hdmiControlEnabled) throws Exception { MockitoAnnotations.initMocks(this); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + setHdmiControlEnabled(hdmiControlEnabled); + PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, mIThermalServiceMock, new Handler(mTestLooper.getLooper())); when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); @@ -146,7 +152,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedWithUnknownTvDevice() { + public void succeedWithUnknownTvDevice() throws Exception { + setUp(true); + HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -185,7 +193,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedAfterGettingPowerStatusOn_Cec14b() { + public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -225,7 +235,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedAfterGettingTransientPowerStatus_Cec14b() { + public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -275,7 +287,9 @@ public class OneTouchPlayActionTest { } @Test - public void timeOut_Cec14b() { + public void timeOut_Cec14b() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -316,7 +330,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedIfPowerStatusOn_Cec20() { + public void succeedIfPowerStatusOn_Cec20() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -348,7 +364,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedIfPowerStatusUnknown_Cec20() { + public void succeedIfPowerStatusUnknown_Cec20() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -390,7 +408,9 @@ public class OneTouchPlayActionTest { } @Test - public void succeedIfPowerStatusStandby_Cec20() { + public void succeedIfPowerStatusStandby_Cec20() throws Exception { + setUp(true); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); @@ -431,6 +451,73 @@ public class OneTouchPlayActionTest { assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } + @Test + public void succeedWithAddressNotAllocated_Cec14b() throws Exception { + setUp(false); + + assertThat(mHdmiControlService.isAddressAllocated()).isFalse(); + + HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( + mHdmiControlService); + playbackDevice.init(); + mLocalDevices.add(playbackDevice); + + TestCallback callback = new TestCallback(); + + mHdmiControlService.oneTouchPlay(callback); + mTestLooper.dispatchAll(); + + assertThat(callback.hasResult()).isFalse(); + assertThat(playbackDevice.isActiveSource()).isFalse(); + + setHdmiControlEnabled(true); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + + mTestLooper.dispatchAll(); + + HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + playbackDevice.mAddress, + HdmiControlManager.POWER_STATUS_ON + ); + mNativeWrapper.onCecMessage(reportPowerStatusMessage); + + mTestLooper.dispatchAll(); + + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + assertThat(playbackDevice.isActiveSource()).isTrue(); + } + + @Test + public void succeedWithAddressAllocated_Cec14b() throws Exception { + setUp(true); + + HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( + mHdmiControlService); + playbackDevice.init(); + mLocalDevices.add(playbackDevice); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + + TestCallback callback = new TestCallback(); + mHdmiControlService.oneTouchPlay(callback); + + HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + playbackDevice.mAddress, + HdmiControlManager.POWER_STATUS_ON + ); + mNativeWrapper.onCecMessage(reportPowerStatusMessage); + + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + assertThat(playbackDevice.isActiveSource()).isTrue(); + } + private static class TestActionTimer implements ActionTimer { private int mState; @@ -456,9 +543,19 @@ public class OneTouchPlayActionTest { mCallbackResult.add(result); } + private boolean hasResult() { + return mCallbackResult.size() != 0; + } + private int getResult() { assertThat(mCallbackResult.size()).isEqualTo(1); return mCallbackResult.get(0); } } + + private void setHdmiControlEnabled(boolean enabled) { + int value = enabled ? 1 : 0; + Settings.Global.putInt(mContextSpy.getContentResolver(), + Settings.Global.HDMI_CONTROL_ENABLED, value); + } } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index fb01ff6e16c6..d405113d064c 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1817,7 +1817,7 @@ public class NetworkPolicyManagerServiceTest { // yet reached. final NetworkPolicyManagerInternal npmi = LocalServices .getService(NetworkPolicyManagerInternal.class); - npmi.onStatsProviderLimitReached("TEST"); + npmi.onStatsProviderWarningOrLimitReached("TEST"); // Verifies that the limit reached leads to a force update and new limit should be set. postMsgAndWaitForCompletion(); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 1ab70e524d3c..212a9c614632 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -641,7 +641,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles, List<String> schemasNotPlatformSurfaceable, Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, - int userId, IAppSearchResultCallback callback) throws RemoteException { + int userId, int version, IAppSearchResultCallback callback) throws RemoteException { for (Map.Entry<String, List<Bundle>> entry : schemasPackageAccessibleBundles.entrySet()) { final String key = entry.getKey(); @@ -666,6 +666,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + public void getNamespaces(String packageName, String databaseName, int userId, + IAppSearchResultCallback callback) throws RemoteException { + ignore(callback); + } + + @Override public void putDocuments(String packageName, String databaseName, List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback) throws RemoteException { @@ -764,7 +770,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @Override public void reportUsage(String packageName, String databaseName, String namespace, - String uri, long usageTimeMillis, int userId, IAppSearchResultCallback callback) + String uri, long usageTimeMillis, boolean systemUsage, int userId, + IAppSearchResultCallback callback) throws RemoteException { ignore(callback); } diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt index c6e35cf84355..e9329053c740 100644 --- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt @@ -93,3 +93,24 @@ inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) = spyThrowOnUnmocked<T>(null, block) inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java) + +/** + * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when + * null is returned. + * + * Generic T is nullable because implicitly bounded by Any?. + */ +fun <T> any(type: Class<T>): T = Mockito.any<T>(type) + +/** + * Wrapper around [Mockito.any] for generic types. + */ +inline fun <reified T> any() = any(T::class.java) + +/** + * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when + * null is returned. + * + * Generic T is nullable because implicitly bounded by Any?. + */ +fun <T> eq(obj: T): T = Mockito.eq<T>(obj) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 16e0d90ac2d3..ed5f1d8c9fc5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -38,6 +38,7 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.window.WindowContext; import androidx.test.filters.SmallTest; @@ -208,7 +209,7 @@ public class WindowTokenTests extends WindowTestsBase { /** * Test that {@link android.view.SurfaceControl} should not be created for the - * {@link WindowToken} which was created for {@link android.app.WindowContext} initially, the + * {@link WindowToken} which was created for {@link WindowContext} initially, the * surface should be create after addWindow for this token. */ @Test diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index 748e5dfe4e91..22a6445843a4 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -7,6 +7,14 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsAssistTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ] } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index c9b0a3e30ca3..0d4c3023c5ec 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -54,7 +54,6 @@ import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.system.OsConstants; -import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.IWindowManager; @@ -64,6 +63,7 @@ import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.server.LocalServices; +import com.android.server.wm.ActivityAssistInfo; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; @@ -187,24 +187,23 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mSessionComponentName, mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid, mHandler); } - List<Pair<IBinder, Integer>> allVisibleActivities = + List<ActivityAssistInfo> allVisibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class) .getTopVisibleActivities(); - List<Pair<IBinder, Integer>> visibleActivities = null; + List<ActivityAssistInfo> visibleActivities = null; if (activityToken != null) { visibleActivities = new ArrayList(); int activitiesCount = allVisibleActivities.size(); for (int i = 0; i < activitiesCount; i++) { - if (allVisibleActivities.get(i).first == activityToken) { - visibleActivities.add( - new Pair<>(activityToken, allVisibleActivities.get(i).second)); + ActivityAssistInfo info = allVisibleActivities.get(i); + if (info.getActivityToken() == activityToken) { + visibleActivities.add(info); break; } } } else { - visibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class) - .getTopVisibleActivities(); + visibleActivities = allVisibleActivities; } return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback, visibleActivities); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 428d342a80c9..cc021a9acfe2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -56,7 +56,6 @@ import android.service.voice.IVoiceInteractionSession; import android.service.voice.IVoiceInteractionSessionService; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionSession; -import android.util.Pair; import android.util.Slog; import android.view.IWindowManager; @@ -68,6 +67,7 @@ import com.android.server.am.AssistDataRequester; import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityAssistInfo; import java.io.PrintWriter; import java.util.ArrayList; @@ -102,6 +102,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, IVoiceInteractionSession mSession; IVoiceInteractor mInteractor; ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); + private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>(); AssistDataRequester mAssistDataRequester; IVoiceInteractionSessionShowCallback mShowCallback = @@ -192,7 +193,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, public boolean showLocked(Bundle args, int flags, int disabledContext, IVoiceInteractionSessionShowCallback showCallback, - List<Pair<IBinder, Integer>> topActivities) { + List<ActivityAssistInfo> topActivities) { if (mBound) { if (!mFullyBound) { mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection, @@ -216,7 +217,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, int topActivitiesCount = topActivities.size(); final ArrayList<IBinder> topActivitiesToken = new ArrayList<>(topActivitiesCount); for (int i = 0; i < topActivitiesCount; i++) { - topActivitiesToken.add(topActivities.get(i).first); + topActivitiesToken.add(topActivities.get(i).getActivityToken()); } mAssistDataRequester.requestAssistData(topActivitiesToken, fetchData, @@ -243,8 +244,16 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } else { doHandleAssistWithoutData(topActivities); } - } else if (showCallback != null) { - mPendingShowCallbacks.add(showCallback); + } else { + if (showCallback != null) { + mPendingShowCallbacks.add(showCallback); + } + if (!assistDataRequestNeeded) { + // If no data are required we are not passing trough mAssistDataRequester. As + // a consequence, when a new session is delivered it is needed to process those + // requests manually. + mPendingHandleAssistWithoutData = topActivities; + } } mCallback.onSessionShown(this); return true; @@ -258,17 +267,17 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, return false; } - private void doHandleAssistWithoutData(List<Pair<IBinder, Integer>> topActivities) { + private void doHandleAssistWithoutData(List<ActivityAssistInfo> topActivities) { final int activityCount = topActivities.size(); for (int i = 0; i < activityCount; i++) { - final Pair<IBinder, Integer> topActivity = topActivities.get(i); - final IBinder activityId = topActivity.first; - final int taskId = topActivity.second; + final ActivityAssistInfo topActivity = topActivities.get(i); + final IBinder assistToken = topActivity.getAssistToken(); + final int taskId = topActivity.getTaskId(); final int activityIndex = i; try { mSession.handleAssist( taskId, - activityId, + assistToken, /* assistData = */ null, /* assistStructure = */ null, /* assistContent = */ null, @@ -468,6 +477,10 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } catch (RemoteException e) { } mAssistDataRequester.processPendingAssistData(); + if (!mPendingHandleAssistWithoutData.isEmpty()) { + doHandleAssistWithoutData(mPendingHandleAssistWithoutData); + mPendingHandleAssistWithoutData.clear(); + } } return true; } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index f110daecd952..2d06062cfa44 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -564,6 +564,7 @@ public class ServiceState implements Parcelable { * @hide */ @UnsupportedAppUsage + @TestApi public int getDataRegState() { return mDataRegState; } diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java index 2f89bfb60d3d..88c66acdca6f 100644 --- a/telephony/java/android/telephony/TelephonyDisplayInfo.java +++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java @@ -84,7 +84,7 @@ public final class TelephonyDisplayInfo implements Parcelable { * </ul> * One of the use case is that UX can show a different icon, for example, "5G+" */ - public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; + public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5; @NetworkType private final int mNetworkType; @@ -186,7 +186,8 @@ public final class TelephonyDisplayInfo implements Parcelable { case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA"; case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO"; case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA"; - case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE"; + case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE"; + case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_ADVANCED"; default: return "UNKNOWN"; } } diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java index f49d4fcab5f2..4259a8620727 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java @@ -32,7 +32,7 @@ public class HierrarchicalDataClassBase implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -98,8 +98,8 @@ public class HierrarchicalDataClassBase implements Parcelable { }; @DataClass.Generated( - time = 1604522375155L, - codegenVersion = "1.0.20", + time = 1616541542813L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java", inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java index e8cce23fa324..677094b14fd6 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java @@ -46,7 +46,7 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -120,8 +120,8 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { }; @DataClass.Generated( - time = 1604522376059L, - codegenVersion = "1.0.20", + time = 1616541543730L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java", inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java index 9de65522fccd..eb260ab6d35d 100644 --- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java @@ -54,7 +54,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -412,8 +412,8 @@ public class ParcelAllTheThingsDataClass implements Parcelable { } @DataClass.Generated( - time = 1604522374190L, - codegenVersion = "1.0.20", + time = 1616541541942L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java", inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java index 5a3e273275ed..158e0656b574 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java @@ -23,8 +23,11 @@ import android.annotation.Size; import android.annotation.StringDef; import android.annotation.StringRes; import android.annotation.UserIdInt; +import android.companion.ICompanionDeviceManager; import android.content.pm.PackageManager; import android.net.LinkAddress; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.accessibility.AccessibilityNodeInfo; @@ -282,6 +285,16 @@ public final class SampleDataClass implements Parcelable { /** + * Binder types are also supported + */ + private @NonNull IBinder mToken = new Binder(); + /** + * AIDL interface types are also supported + */ + private @Nullable ICompanionDeviceManager mIPCInterface = null; + + + /** * Manually declaring any method that would otherwise be generated suppresses its generation, * allowing for fine-grained overrides of the generated behavior. */ @@ -344,7 +357,7 @@ public final class SampleDataClass implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -492,6 +505,10 @@ public final class SampleDataClass implements Parcelable { * * Validation annotations following {@link Each} annotation, will be applied for each * array/collection element instead. + * @param token + * Binder types are also supported + * @param iPCInterface + * AIDL interface types are also supported */ @DataClass.Generated.Member public SampleDataClass( @@ -514,7 +531,9 @@ public final class SampleDataClass implements Parcelable { @Nullable LinkAddress[] linkAddresses5, @StringRes int stringRes, @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek, - @Size(2) @NonNull @FloatRange(from = 0f) float[] coords) { + @Size(2) @NonNull @FloatRange(from = 0f) float[] coords, + @NonNull IBinder token, + @Nullable ICompanionDeviceManager iPCInterface) { this.mNum = num; this.mNum2 = num2; this.mNum4 = num4; @@ -597,6 +616,10 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -797,6 +820,22 @@ public final class SampleDataClass implements Parcelable { } /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull IBinder getToken() { + return mToken; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @Nullable ICompanionDeviceManager getIPCInterface() { + return mIPCInterface; + } + + /** * When using transient fields for caching it's often also a good idea to initialize them * lazily. * @@ -1089,6 +1128,26 @@ public final class SampleDataClass implements Parcelable { return this; } + /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull SampleDataClass setToken(@NonNull IBinder value) { + mToken = value; + AnnotationValidations.validate( + NonNull.class, null, mToken); + return this; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @NonNull SampleDataClass setIPCInterface(@NonNull ICompanionDeviceManager value) { + mIPCInterface = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -1115,7 +1174,9 @@ public final class SampleDataClass implements Parcelable { "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " + "stringRes = " + mStringRes + ", " + "dayOfWeek = " + mDayOfWeek + ", " + - "coords = " + java.util.Arrays.toString(mCoords) + + "coords = " + java.util.Arrays.toString(mCoords) + ", " + + "token = " + mToken + ", " + + "iPCInterface = " + mIPCInterface + " }"; } @@ -1151,7 +1212,9 @@ public final class SampleDataClass implements Parcelable { && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5) && mStringRes == that.mStringRes && mDayOfWeek == that.mDayOfWeek - && java.util.Arrays.equals(mCoords, that.mCoords); + && java.util.Arrays.equals(mCoords, that.mCoords) + && Objects.equals(mToken, that.mToken) + && Objects.equals(mIPCInterface, that.mIPCInterface); } @Override @@ -1181,6 +1244,8 @@ public final class SampleDataClass implements Parcelable { _hash = 31 * _hash + mStringRes; _hash = 31 * _hash + mDayOfWeek; _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords); + _hash = 31 * _hash + Objects.hashCode(mToken); + _hash = 31 * _hash + Objects.hashCode(mIPCInterface); return _hash; } @@ -1208,6 +1273,8 @@ public final class SampleDataClass implements Parcelable { actionInt.acceptInt(this, "stringRes", mStringRes); actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek); actionObject.acceptObject(this, "coords", mCoords); + actionObject.acceptObject(this, "token", mToken); + actionObject.acceptObject(this, "iPCInterface", mIPCInterface); } /** @deprecated May cause boxing allocations - use with caution! */ @@ -1234,6 +1301,8 @@ public final class SampleDataClass implements Parcelable { action.acceptObject(this, "stringRes", mStringRes); action.acceptObject(this, "dayOfWeek", mDayOfWeek); action.acceptObject(this, "coords", mCoords); + action.acceptObject(this, "token", mToken); + action.acceptObject(this, "iPCInterface", mIPCInterface); } @DataClass.Generated.Member @@ -1269,6 +1338,7 @@ public final class SampleDataClass implements Parcelable { if (mOtherParcelable != null) flg |= 0x40; if (mLinkAddresses4 != null) flg |= 0x800; if (mLinkAddresses5 != null) flg |= 0x10000; + if (mIPCInterface != null) flg |= 0x200000; dest.writeLong(flg); dest.writeInt(mNum); dest.writeInt(mNum2); @@ -1290,6 +1360,8 @@ public final class SampleDataClass implements Parcelable { dest.writeInt(mStringRes); dest.writeInt(mDayOfWeek); dest.writeFloatArray(mCoords); + dest.writeStrongBinder(mToken); + if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface); } @Override @@ -1326,6 +1398,8 @@ public final class SampleDataClass implements Parcelable { int stringRes = in.readInt(); int dayOfWeek = in.readInt(); float[] coords = in.createFloatArray(); + IBinder token = (IBinder) in.readStrongBinder(); + ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder()); this.mNum = num; this.mNum2 = num2; @@ -1409,6 +1483,10 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -1454,6 +1532,8 @@ public final class SampleDataClass implements Parcelable { private @StringRes int mStringRes; private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek; private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords; + private @NonNull IBinder mToken; + private @Nullable ICompanionDeviceManager mIPCInterface; private long mBuilderFieldsSet = 0L; @@ -1794,10 +1874,32 @@ public final class SampleDataClass implements Parcelable { return this; } + /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull Builder setToken(@NonNull IBinder value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100000; + mToken = value; + return this; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x200000; + mIPCInterface = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull SampleDataClass build() { checkNotUsed(); - mBuilderFieldsSet |= 0x100000; // Mark builder used + mBuilderFieldsSet |= 0x400000; // Mark builder used if ((mBuilderFieldsSet & 0x10) == 0) { mName2 = "Bob"; @@ -1841,6 +1943,12 @@ public final class SampleDataClass implements Parcelable { if ((mBuilderFieldsSet & 0x80000) == 0) { mCoords = new float[] { 0f, 0f }; } + if ((mBuilderFieldsSet & 0x100000) == 0) { + mToken = new Binder(); + } + if ((mBuilderFieldsSet & 0x200000) == 0) { + mIPCInterface = null; + } SampleDataClass o = new SampleDataClass( mNum, mNum2, @@ -1861,12 +1969,14 @@ public final class SampleDataClass implements Parcelable { mLinkAddresses5, mStringRes, mDayOfWeek, - mCoords); + mCoords, + mToken, + mIPCInterface); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x100000) != 0) { + if ((mBuilderFieldsSet & 0x400000) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -1874,10 +1984,10 @@ public final class SampleDataClass implements Parcelable { } @DataClass.Generated( - time = 1604522372172L, - codegenVersion = "1.0.20", + time = 1616541539978L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java", - inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)") + inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)") @Deprecated private void __metadata() {} diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java index 3ab34452f9fc..a535e227cccf 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java @@ -85,7 +85,7 @@ public class SampleWithCustomBuilder implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -253,8 +253,8 @@ public class SampleWithCustomBuilder implements Parcelable { } @DataClass.Generated( - time = 1604522373190L, - codegenVersion = "1.0.20", + time = 1616541540898L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java", inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java index 8901cac1cb1b..d40962456741 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java @@ -36,7 +36,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -135,8 +135,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522377998L, - codegenVersion = "1.0.20", + time = 1616541545539L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated @@ -160,7 +160,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -259,8 +259,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522378007L, - codegenVersion = "1.0.20", + time = 1616541545548L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated @@ -274,7 +274,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -373,8 +373,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522378015L, - codegenVersion = "1.0.20", + time = 1616541545552L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java index ac776f3c2764..3583b95fb4ce 100644 --- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java +++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java @@ -64,7 +64,7 @@ public class StaleDataclassDetectorFalsePositivesTest { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -89,8 +89,8 @@ public class StaleDataclassDetectorFalsePositivesTest { } @DataClass.Generated( - time = 1604522377011L, - codegenVersion = "1.0.20", + time = 1616541544639L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java", inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nprivate @android.annotation.IntRange void annotatedWithConstArg()\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)") @Deprecated diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 2e57467e55fe..c679d0487629 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -25,17 +25,24 @@ android_test_helper_app { name: "StagedInstallInternalTestApp", manifest: "app/AndroidManifest.xml", srcs: ["app/src/**/*.java"], - static_libs: ["androidx.test.rules", "cts-install-lib"], + static_libs: [ + "androidx.test.rules", + "cts-install-lib", + ], test_suites: ["general-tests"], java_resources: [ ":com.android.apex.apkrollback.test_v2", + ":StagedInstallTestApexV2_WrongSha", ], } java_test_host { name: "StagedInstallInternalTest", srcs: ["src/**/*.java"], - libs: ["tradefed", "cts-shim-host-lib"], + libs: [ + "tradefed", + "cts-shim-host-lib", + ], static_libs: [ "testng", "compatibility-tradefed", diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index ad8aac17d844..e633c87d7fbb 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -17,6 +17,7 @@ package com.android.tests.stagedinstallinternal; import static com.android.cts.install.lib.InstallUtils.getPackageInstaller; +import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -50,6 +51,9 @@ public class StagedInstallInternalTest { private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); + private static final TestApp APEX_WRONG_SHA_V2 = new TestApp( + "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, + "com.android.apex.cts.shim.v2_wrong_sha.apex"); private File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), @@ -128,6 +132,26 @@ public class StagedInstallInternalTest { } @Test + public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception { + InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed", + Install.single(APEX_WRONG_SHA_V2).setStaged()); + } + + @Test + public void testStagedSessionShouldCleanUpOnOnSuccess_Commit() throws Exception { + int sessionId = Install.single(TestApp.A1).setStaged().commit(); + storeSessionId(sessionId); + } + + @Test + public void testStagedSessionShouldCleanUpOnOnSuccess_Verify() throws Exception { + int sessionId = retrieveLastSessionId(); + PackageInstaller.SessionInfo info = InstallUtils.getStagedSessionInfo(sessionId); + assertThat(info).isNotNull(); + assertThat(info.isStagedSessionApplied()).isTrue(); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK", Install.single(TestApp.AIncompleteSplit).setStaged()); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index 8dc53ac26715..9fd190cc12fe 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -44,7 +44,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -252,6 +251,28 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } @Test + public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + List<String> before = getStagingDirectories(); + runPhase("testStagedSessionShouldCleanUpOnVerificationFailure"); + List<String> after = getStagingDirectories(); + assertThat(after).isEqualTo(before); + } + + @Test + @LargeTest + public void testStagedSessionShouldCleanUpOnOnSuccess() throws Exception { + List<String> before = getStagingDirectories(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Commit"); + assertThat(getStagingDirectories()).isNotEqualTo(before); + getDevice().reboot(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify"); + List<String> after = getStagingDirectories(); + assertThat(after).isEqualTo(before); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { List<String> before = getStagingDirectories(); runPhase("testStagedInstallationShouldCleanUpOnValidationFailure"); @@ -273,12 +294,14 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { //create random directories in /data/app-staging folder getDevice().enableAdbRoot(); getDevice().executeShellCommand("mkdir /data/app-staging/session_123"); - getDevice().executeShellCommand("mkdir /data/app-staging/random_name"); + getDevice().executeShellCommand("mkdir /data/app-staging/session_456"); getDevice().disableAdbRoot(); - assertThat(getStagingDirectories()).isNotEmpty(); + assertThat(getStagingDirectories()).contains("session_123"); + assertThat(getStagingDirectories()).contains("session_456"); getDevice().reboot(); - assertThat(getStagingDirectories()).isEmpty(); + assertThat(getStagingDirectories()).doesNotContain("session_123"); + assertThat(getStagingDirectories()).doesNotContain("session_456"); } @Test @@ -304,9 +327,6 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { .stream().filter(entry -> entry.getName().matches("session_\\d+")) .map(entry -> entry.getName()) .collect(Collectors.toList()); - } catch (Exception e) { - // Return an empty list if any error - return Collections.EMPTY_LIST; } finally { getDevice().disableAdbRoot(); } diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt index fd126ad8c0b3..1e54093bf111 100644 --- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -19,6 +19,7 @@ package android.net import android.os.Build import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 +import com.android.modules.utils.build.SdkLevel.isAtLeastS import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.assertParcelSane @@ -44,7 +45,13 @@ class NetworkAgentConfigTest { setPartialConnectivityAcceptable(false) setUnvalidatedConnectivityAcceptable(true) }.build() - assertParcelSane(config, 12) + if (isAtLeastS()) { + // From S, the config will have 12 items + assertParcelSane(config, 12) + } else { + // For R or below, the config will have 10 items + assertParcelSane(config, 10) + } } @Test @IgnoreUpTo(Build.VERSION_CODES.Q) diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java index 11d44b86bc50..cd9da8eaa727 100644 --- a/tests/net/common/java/android/net/NetworkTest.java +++ b/tests/net/common/java/android/net/NetworkTest.java @@ -22,11 +22,16 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.os.Build; import android.platform.test.annotations.AppModeFull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +48,9 @@ import java.net.SocketException; public class NetworkTest { final Network mNetwork = new Network(99); + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + @Test public void testBindSocketOfInvalidFdThrows() throws Exception { @@ -151,6 +159,23 @@ public class NetworkTest { } @Test + public void testFromNetworkHandle() { + final Network network = new Network(1234); + assertEquals(network.getNetId(), + Network.fromNetworkHandle(network.getNetworkHandle()).getNetId()); + } + + // Parsing private DNS bypassing handle was not supported until S + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testFromNetworkHandle_S() { + final Network network = new Network(1234, true); + + final Network recreatedNetwork = Network.fromNetworkHandle(network.getNetworkHandle()); + assertEquals(network.netId, recreatedNetwork.netId); + assertEquals(network.getNetIdForResolv(), recreatedNetwork.getNetIdForResolv()); + } + + @Test public void testGetPrivateDnsBypassingCopy() { final Network copy = mNetwork.getPrivateDnsBypassingCopy(); assertEquals(mNetwork.netId, copy.netId); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 16a5fcd1ef30..fd376528aa44 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -30,10 +30,13 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED; +import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE; -import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; @@ -91,10 +94,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER; -import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER; -import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; @@ -1201,12 +1200,10 @@ public class ConnectivityServiceTest { mNetworkCapabilities); mMockNetworkAgent.waitForIdle(TIMEOUT_MS); - final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET - : mMockVpn.getNetwork().getNetId(); - verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId), + verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()), eq(toUidRangeStableParcels(uids))); verify(mMockNetd, never()) - .networkRemoveUidRanges(eq(expectedNetId), any()); + .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any()); mAgentRegistered = true; updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent"); mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); @@ -9799,14 +9796,13 @@ public class ConnectivityServiceTest { exemptUidCaptor.capture()); assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); - final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET - : mMockVpn.getNetwork().getNetId(); - if (add) { - inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId), + inOrder.verify(mMockNetd, times(1)) + .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()), eq(toUidRangeStableParcels(vpnRanges))); } else { - inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId), + inOrder.verify(mMockNetd, times(1)) + .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), eq(toUidRangeStableParcels(vpnRanges))); } diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 8932892c3aec..bcd6ed73e133 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -59,9 +59,10 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.RoundedCornersTest", "android.view.WindowMetricsTest", "android.view.PendingInsetsControllerTest", - "android.app.WindowContextTest", + "android.window.WindowContextTest", "android.window.WindowMetricsHelperTest", - "android.app.activity.ActivityThreadTest" + "android.app.activity.ActivityThreadTest", + "android.window.WindowContextControllerTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index a9d5822be226..babea364edbe 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -19,6 +19,8 @@ package com.android.server; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; +import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -238,9 +240,14 @@ public class VcnManagementServiceTest { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) .getSubscriptionsInGroup(any()); - doReturn(isPrivileged) + doReturn(mTelMgr) .when(mTelMgr) - .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); + .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); + doReturn(isPrivileged + ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS + : CARRIER_PRIVILEGE_STATUS_NO_ACCESS) + .when(mTelMgr) + .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME)); } @Test @@ -391,7 +398,7 @@ public class VcnManagementServiceTest { mTestLooper.moveTimeForward( VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); mTestLooper.dispatchAll(); - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); // Verify that new instance was different, and the old one was torn down @@ -492,7 +499,7 @@ public class VcnManagementServiceTest { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } @@ -505,7 +512,7 @@ public class VcnManagementServiceTest { .getBinderCallingUid(); try { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } @@ -516,15 +523,24 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(false); try { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test + public void testClearVcnConfigMismatchedPackages() throws Exception { + try { + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage"); + fail("Expected security exception due to mismatched packages"); + } catch (SecurityException expected) { + } + } + + @Test public void testClearVcnConfig() throws Exception { - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @@ -535,7 +551,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); } @@ -564,7 +580,7 @@ public class VcnManagementServiceTest { verify(vcnInstance).updateConfig(TEST_VCN_CONFIG); // Verify Vcn is stopped if it was already started - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(vcnInstance).teardownAsynchronously(); } @@ -781,7 +797,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(mMockPolicyListener).onPolicyChanged(); } diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt index 02ebaef90f0b..74392ddc30e6 100644 --- a/tools/codegen/src/com/android/codegen/FieldInfo.kt +++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt @@ -220,11 +220,12 @@ data class FieldInfo( isBinder(FieldInnerType!!) -> "BinderList" else -> "ParcelableList" } + isStrongBinder(Type) -> "StrongBinder" isIInterface(Type) -> "StrongInterface" - isBinder(Type) -> "StrongBinder" else -> "TypedObject" }.capitalize() + private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder" private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type) private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase() }
\ No newline at end of file diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt index d9ad649782bb..4da401951470 100644 --- a/tools/codegen/src/com/android/codegen/SharedConstants.kt +++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt @@ -1,7 +1,7 @@ package com.android.codegen const val CODEGEN_NAME = "codegen" -const val CODEGEN_VERSION = "1.0.22" +const val CODEGEN_VERSION = "1.0.23" const val CANONICAL_BUILDER_CLASS = "Builder" const val BASE_BUILDER_CLASS = "BaseBuilder" diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index c64f4bc605f1..da0571ba88e1 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -101,6 +101,7 @@ public class WifiNl80211Manager { // Cached wificond binder handlers. private IWificond mWificond; + private WificondEventHandler mWificondEventHandler = new WificondEventHandler(); private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>(); private HashMap<String, IApInterface> mApInterfaces = new HashMap<>(); private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>(); @@ -114,6 +115,18 @@ public class WifiNl80211Manager { private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false); /** + * Interface used to listen country code event + */ + public interface CountryCodeChangeListener { + /** + * Called when country code changed. + * + * @param countryCode A new country code which is 2-Character alphanumeric. + */ + void onChanged(@NonNull String countryCode); + } + + /** * Interface used when waiting for scans to be completed (with results). */ public interface ScanEventCallback { @@ -147,6 +160,46 @@ public class WifiNl80211Manager { void onPnoRequestFailed(); } + /** @hide */ + @VisibleForTesting + public class WificondEventHandler extends IWificondEventCallback.Stub { + private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder = + new HashMap<>(); + + /** + * Register CountryCodeChangeListener with pid. + * + * @param executor The Executor on which to execute the callbacks. + * @param listener listener for country code changed events. + */ + public void registerCountryCodeChangeListener(Executor executor, + CountryCodeChangeListener listener) { + mCountryCodeChangeListenerHolder.put(listener, executor); + } + + /** + * Unregister CountryCodeChangeListener with pid. + * + * @param listener listener which registered country code changed events. + */ + public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) { + mCountryCodeChangeListenerHolder.remove(listener); + } + + @Override + public void OnRegDomainChanged(String countryCode) { + Log.d(TAG, "OnRegDomainChanged " + countryCode); + final long token = Binder.clearCallingIdentity(); + try { + mCountryCodeChangeListenerHolder.forEach((listener, executor) -> { + executor.execute(() -> listener.onChanged(countryCode)); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + private class ScanEventHandler extends IScanEvent.Stub { private Executor mExecutor; private ScanEventCallback mCallback; @@ -347,6 +400,12 @@ public class WifiNl80211Manager { mWificond = wificond; } + /** @hide */ + @VisibleForTesting + public WificondEventHandler getWificondEventHandler() { + return mWificondEventHandler; + } + private class PnoScanEventHandler extends IPnoScanEvent.Stub { private Executor mExecutor; private ScanEventCallback mCallback; @@ -574,6 +633,7 @@ public class WifiNl80211Manager { } try { mWificond.asBinder().linkToDeath(() -> binderDied(), 0); + mWificond.registerWificondEventCallback(mWificondEventHandler); } catch (RemoteException e) { Log.e(TAG, "Failed to register death notification for wificond"); // The remote has already died. @@ -1174,6 +1234,34 @@ public class WifiNl80211Manager { } /** + * Register the provided listener for country code event. + * + * @param executor The Executor on which to execute the callbacks. + * @param listener listener for country code changed events. + * @return true on success, false on failure. + */ + public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor, + @NonNull CountryCodeChangeListener listener) { + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + Log.d(TAG, "registerCountryCodeEventListener called"); + mWificondEventHandler.registerCountryCodeChangeListener(executor, listener); + return true; + } + + + /** + * Unregister CountryCodeChangeListener with pid. + * + * @param listener listener which registered country code changed events. + */ + public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) { + Log.d(TAG, "unregisterCountryCodeEventListener called"); + mWificondEventHandler.unregisterCountryCodeChangeListener(listener); + } + + /** * Register the provided callback handler for SoftAp events. The interface must first be created * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 4b03a49e40bb..98a0042a7096 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -97,14 +98,20 @@ public class WifiNl80211ManagerTest { @Mock private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback; @Mock + private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener; + @Mock + private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2; + @Mock private Context mContext; private TestLooper mLooper; private TestAlarmManager mTestAlarmManager; private AlarmManager mAlarmManager; private WifiNl80211Manager mWificondControl; + private WifiNl80211Manager.WificondEventHandler mWificondEventHandler; private static final String TEST_INTERFACE_NAME = "test_wlan_if"; private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1"; private static final String TEST_INVALID_INTERFACE_NAME = "asdf"; + private static final String TEST_COUNTRY_CODE = "US"; private static final byte[] TEST_SSID = new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'}; private static final byte[] TEST_PSK = @@ -182,6 +189,7 @@ public class WifiNl80211ManagerTest { when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl); when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME); mWificondControl = new WifiNl80211Manager(mContext, mWificond); + mWificondEventHandler = mWificondControl.getWificondEventHandler(); assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run, mNormalScanCallback, mPnoScanCallback)); @@ -760,6 +768,28 @@ public class WifiNl80211ManagerTest { } /** + * Ensures callback works after register CountryCodeChangeListener. + */ + @Test + public void testCountryCodeChangeListenerInvocation() throws Exception { + assertTrue(mWificondControl.registerCountryCodeChangeListener( + Runnable::run, mCountryCodeChangeListener)); + assertTrue(mWificondControl.registerCountryCodeChangeListener( + Runnable::run, mCountryCodeChangeListener2)); + + mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE); + + reset(mCountryCodeChangeListener); + reset(mCountryCodeChangeListener2); + mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2); + mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE); + } + + /** * Verifies registration and invocation of wificond death handler. */ @Test |